Merge branch 'cdata' of https://github.com/w00000dy/WLED into cdata

This commit is contained in:
Woody 2025-02-01 22:54:20 +01:00
commit dfd7ff5b39
No known key found for this signature in database
95 changed files with 5250 additions and 4068 deletions

View File

@ -2,12 +2,7 @@
# [Choice] Python version: 3, 3.9, 3.8, 3.7, 3.6 # [Choice] Python version: 3, 3.9, 3.8, 3.7, 3.6
ARG VARIANT="3" ARG VARIANT="3"
FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT} FROM mcr.microsoft.com/devcontainers/python:0-${VARIANT}
# [Option] Install Node.js
ARG INSTALL_NODE="true"
ARG NODE_VERSION="lts/*"
RUN if [ "${INSTALL_NODE}" = "true" ]; then su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi
# [Optional] If your pip requirements rarely change, uncomment this section to add them to the image. # [Optional] If your pip requirements rarely change, uncomment this section to add them to the image.
# COPY requirements.txt /tmp/pip-tmp/ # COPY requirements.txt /tmp/pip-tmp/

View File

@ -5,10 +5,7 @@
"context": "..", "context": "..",
"args": { "args": {
// Update 'VARIANT' to pick a Python version: 3, 3.6, 3.7, 3.8, 3.9 // Update 'VARIANT' to pick a Python version: 3, 3.6, 3.7, 3.8, 3.9
"VARIANT": "3", "VARIANT": "3"
// Options
"INSTALL_NODE": "true",
"NODE_VERSION": "lts/*"
} }
}, },
@ -27,7 +24,8 @@
// risk to running the build directly on the host. // risk to running the build directly on the host.
// "runArgs": ["--privileged", "-v", "/dev/bus/usb:/dev/bus/usb", "--group-add", "dialout"], // "runArgs": ["--privileged", "-v", "/dev/bus/usb:/dev/bus/usb", "--group-add", "dialout"],
// Set *default* container specific settings.json values on container create. "customizations": {
"vscode": {
"settings": { "settings": {
"terminal.integrated.shell.linux": "/bin/bash", "terminal.integrated.shell.linux": "/bin/bash",
"python.pythonPath": "/usr/local/bin/python", "python.pythonPath": "/usr/local/bin/python",
@ -43,18 +41,18 @@
"python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle", "python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle",
"python.linting.pylintPath": "/usr/local/py-utils/bin/pylint" "python.linting.pylintPath": "/usr/local/py-utils/bin/pylint"
}, },
// Add the IDs of extensions you want installed when the container is created.
"extensions": [ "extensions": [
"ms-python.python", "ms-python.python",
"platformio.platformio-ide" "platformio.platformio-ide"
], ]
}
},
// Use 'forwardPorts' to make a list of ports inside the container available locally. // Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [], // "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created. // Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "npm install", "postCreateCommand": "bash -i -c 'nvm install && npm ci'",
// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode" "remoteUser": "vscode"

81
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,81 @@
name: WLED Build
# Only included into other workflows
on:
workflow_call:
jobs:
get_default_envs:
name: Gather Environments
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
- name: Install PlatformIO
run: pip install -r requirements.txt
- name: Get default environments
id: envs
run: |
echo "environments=$(pio project config --json-output | jq -cr '.[0][1][0][1]')" >> $GITHUB_OUTPUT
outputs:
environments: ${{ steps.envs.outputs.environments }}
build:
name: Build Enviornments
runs-on: ubuntu-latest
needs: get_default_envs
strategy:
fail-fast: false
matrix:
environment: ${{ fromJSON(needs.get_default_envs.outputs.environments) }}
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- run: npm ci
- name: Cache PlatformIO
uses: actions/cache@v4
with:
path: |
~/.platformio/.cache
~/.buildcache
build_output
key: pio-${{ runner.os }}-${{ matrix.environment }}-${{ hashFiles('platformio.ini', 'pio-scripts/output_bins.py') }}-${{ hashFiles('wled00/**', 'usermods/**') }}
restore-keys: pio-${{ runner.os }}-${{ matrix.environment }}-${{ hashFiles('platformio.ini', 'pio-scripts/output_bins.py') }}-
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
- name: Install PlatformIO
run: pip install -r requirements.txt
- name: Build firmware
run: pio run -e ${{ matrix.environment }}
- uses: actions/upload-artifact@v4
with:
name: firmware-${{ matrix.environment }}
path: |
build_output/release/*.bin
build_output/release/*_ESP02*.bin.gz
testCdata:
name: Test cdata.js
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- run: npm ci
- run: npm test

41
.github/workflows/nightly.yml vendored Normal file
View File

@ -0,0 +1,41 @@
name: Deploy Nightly
on:
# This can be used to automatically publish nightlies at UTC nighttime
schedule:
- cron: '0 2 * * *' # run at 2 AM UTC
# This can be used to allow manually triggering nightlies from the web interface
workflow_dispatch:
jobs:
wled_build:
uses: ./.github/workflows/build.yml
nightly:
name: Deploy nightly
runs-on: ubuntu-latest
needs: wled_build
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
merge-multiple: true
- name: Show Files
run: ls -la
- name: "✏️ Generate release changelog"
id: changelog
uses: janheinrichmerker/action-github-changelog-generator@v2.3
with:
token: ${{ secrets.GITHUB_TOKEN }}
sinceTag: v0.15.0
- name: Update Nightly Release
uses: andelf/nightly-release@main
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: nightly
name: 'Nightly Release $$'
prerelease: true
body: ${{ steps.changelog.outputs.changelog }}
files: |
*.bin
*.bin.gz

28
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,28 @@
name: WLED Release CI
on:
push:
tags:
- '*'
jobs:
wled_build:
uses: ./.github/workflows/build.yml
release:
name: Create Release
runs-on: ubuntu-latest
needs: wled_build
steps:
- uses: actions/download-artifact@v4
with:
merge-multiple: true
- name: Create draft release
uses: softprops/action-gh-release@v1
with:
draft: True
files: |
*.bin
*.bin.gz

View File

@ -1,94 +1,11 @@
name: WLED CI name: WLED CI
on: [push, pull_request] on:
push:
branches:
- '*'
pull_request:
jobs: jobs:
wled_build:
get_default_envs: uses: ./.github/workflows/build.yml
name: Gather Environments
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
- name: Install PlatformIO
run: pip install -r requirements.txt
- name: Get default environments
id: envs
run: |
echo "environments=$(pio project config --json-output | jq -cr '.[0][1][0][1]')" >> $GITHUB_OUTPUT
outputs:
environments: ${{ steps.envs.outputs.environments }}
build:
name: Build Enviornments
runs-on: ubuntu-latest
needs: get_default_envs
strategy:
fail-fast: false
matrix:
environment: ${{ fromJSON(needs.get_default_envs.outputs.environments) }}
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
cache: 'npm'
- run: npm ci
- name: Cache PlatformIO
uses: actions/cache@v4
with:
path: |
~/.platformio/.cache
~/.buildcache
build_output
key: pio-${{ runner.os }}-${{ matrix.environment }}-${{ hashFiles('platformio.ini', 'pio-scripts/output_bins.py') }}-${{ hashFiles('wled00/**', 'usermods/**') }}
restore-keys: pio-${{ runner.os }}-${{ matrix.environment }}-${{ hashFiles('platformio.ini', 'pio-scripts/output_bins.py') }}-
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
- name: Install PlatformIO
run: pip install -r requirements.txt
- name: Build firmware
run: pio run -e ${{ matrix.environment }}
- uses: actions/upload-artifact@v4
with:
name: firmware-${{ matrix.environment }}
path: |
build_output/release/*.bin
build_output/release/*_ESP02*.bin.gz
release:
name: Create Release
runs-on: ubuntu-latest
needs: build
if: startsWith(github.ref, 'refs/tags/')
steps:
- uses: actions/download-artifact@v4
with:
merge-multiple: true
- name: Create draft release
uses: softprops/action-gh-release@v1
with:
draft: True
files: |
*.bin
*.bin.gz
testCdata:
name: Test cdata.js
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
cache: 'npm'
- run: npm ci
- run: npm test

1
.nvmrc Normal file
View File

@ -0,0 +1 @@
20.18

View File

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

View File

@ -14,7 +14,7 @@ A good description helps us to review and understand your proposed changes. For
### Target branch for pull requests ### Target branch for pull requests
Please make all PRs against the `0_15` branch. Please make all PRs against the `main` branch.
### Updating your code ### Updating your code
While the PR is open - and under review by maintainers - you may be asked to modify your PR source code. While the PR is open - and under review by maintainers - you may be asked to modify your PR source code.

47
boards/lolin_s3_mini.json Normal file
View File

@ -0,0 +1,47 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"memory_type": "qio_qspi"
},
"core": "esp32",
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_LOLIN_S3_MINI",
"-DARDUINO_USB_MODE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"hwids": [
[
"0x303A",
"0x8167"
]
],
"mcu": "esp32s3",
"variant": "lolin_s3_mini"
},
"connectivity": [
"bluetooth",
"wifi"
],
"debug": {
"openocd_target": "esp32s3.cfg"
},
"frameworks": [
"arduino",
"espidf"
],
"name": "WEMOS LOLIN S3 Mini",
"upload": {
"flash_size": "4MB",
"maximum_ram_size": 327680,
"maximum_size": 4194304,
"require_upload_port": true,
"speed": 460800
},
"url": "https://www.wemos.cc/en/latest/s3/index.html",
"vendor": "WEMOS"
}

View File

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

View File

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

7
package-lock.json generated
View File

@ -1,18 +1,21 @@
{ {
"name": "wled", "name": "wled",
"version": "0.15.0-b7", "version": "0.16.0-dev",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "wled", "name": "wled",
"version": "0.15.0-b7", "version": "0.16.0-dev",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"clean-css": "^5.3.3", "clean-css": "^5.3.3",
"html-minifier-terser": "^7.2.0", "html-minifier-terser": "^7.2.0",
"nodemon": "^3.1.9", "nodemon": "^3.1.9",
"web-resource-inliner": "^7.0.0" "web-resource-inliner": "^7.0.0"
},
"engines": {
"node": ">=20.0.0"
} }
}, },
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {

View File

@ -1,6 +1,6 @@
{ {
"name": "wled", "name": "wled",
"version": "0.15.0-b7", "version": "0.16.0-alpha",
"description": "Tools for WLED project", "description": "Tools for WLED project",
"main": "tools/cdata.js", "main": "tools/cdata.js",
"directories": { "directories": {
@ -27,5 +27,8 @@
"html-minifier-terser": "^7.2.0", "html-minifier-terser": "^7.2.0",
"web-resource-inliner": "^7.0.0", "web-resource-inliner": "^7.0.0",
"nodemon": "^3.1.9" "nodemon": "^3.1.9"
},
"engines": {
"node": ">=20.0.0"
} }
} }

View File

@ -19,8 +19,9 @@ def _create_dirs(dirs=["map", "release", "firmware"]):
os.makedirs(os.path.join(OUTPUT_DIR, d), exist_ok=True) os.makedirs(os.path.join(OUTPUT_DIR, d), exist_ok=True)
def create_release(source): def create_release(source):
release_name = _get_cpp_define_value(env, "WLED_RELEASE_NAME") release_name_def = _get_cpp_define_value(env, "WLED_RELEASE_NAME")
if release_name: if release_name_def:
release_name = release_name_def.replace("\\\"", "")
version = _get_cpp_define_value(env, "WLED_VERSION") version = _get_cpp_define_value(env, "WLED_VERSION")
release_file = os.path.join(OUTPUT_DIR, "release", f"WLED_{version}_{release_name}.bin") release_file = os.path.join(OUTPUT_DIR, "release", f"WLED_{version}_{release_name}.bin")
release_gz_file = release_file + ".gz" release_gz_file = release_file + ".gz"

View File

@ -10,7 +10,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# CI/release binaries # CI/release binaries
default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev, esp32dev_V4, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover
src_dir = ./wled00 src_dir = ./wled00
data_dir = ./wled00/data data_dir = ./wled00/data
@ -140,7 +140,7 @@ lib_deps =
IRremoteESP8266 @ 2.8.2 IRremoteESP8266 @ 2.8.2
makuna/NeoPixelBus @ 2.8.0 makuna/NeoPixelBus @ 2.8.0
#https://github.com/makuna/NeoPixelBus.git#CoreShaderBeta #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 # for I2C interface
;Wire ;Wire
# ESP-NOW library # ESP-NOW library
@ -176,6 +176,7 @@ lib_deps =
extra_scripts = ${scripts_defaults.extra_scripts} extra_scripts = ${scripts_defaults.extra_scripts}
[esp8266] [esp8266]
build_unflags = ${common.build_unflags}
build_flags = build_flags =
-DESP8266 -DESP8266
-DFP_IN_IROM -DFP_IN_IROM
@ -235,20 +236,30 @@ lib_deps_compat =
IRremoteESP8266 @ 2.8.2 IRremoteESP8266 @ 2.8.2
makuna/NeoPixelBus @ 2.7.9 makuna/NeoPixelBus @ 2.7.9
https://github.com/blazoncek/QuickESPNow.git#optional-debug 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] [esp32]
#platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.3/platform-espressif32-2.0.2.3.zip #platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.3/platform-espressif32-2.0.2.3.zip
platform = espressif32@3.5.0 platform = espressif32@3.5.0
platform_packages = framework-arduinoespressif32 @ https://github.com/Aircoookie/arduino-esp32.git#1.0.6.4 platform_packages = framework-arduinoespressif32 @ https://github.com/Aircoookie/arduino-esp32.git#1.0.6.4
build_unflags = ${common.build_unflags}
build_flags = -g build_flags = -g
-DARDUINO_ARCH_ESP32 -DARDUINO_ARCH_ESP32
#-DCONFIG_LITTLEFS_FOR_IDF_3_2 #-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 #use LITTLEFS library by lorol in ESP32 core 1.x.x instead of built-in in 2.x.x
-D LOROL_LITTLEFS -D LOROL_LITTLEFS
; -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 ; -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 tiny_partitions = tools/WLED_ESP32_2MB_noOTA.csv
default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
extended_partitions = tools/WLED_ESP32_4MB_700k_FS.csv extended_partitions = tools/WLED_ESP32_4MB_700k_FS.csv
@ -257,12 +268,13 @@ large_partitions = tools/WLED_ESP32_8MB.csv
extreme_partitions = tools/WLED_ESP32_16MB_9MB_FS.csv extreme_partitions = tools/WLED_ESP32_16MB_9MB_FS.csv
lib_deps = lib_deps =
https://github.com/lorol/LITTLEFS.git https://github.com/lorol/LITTLEFS.git
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 ${esp32_all_variants.lib_deps}
${env.lib_deps} ${env.lib_deps}
# additional build flags for audioreactive # additional build flags for audioreactive
AR_build_flags = -D USERMOD_AUDIOREACTIVE AR_build_flags = -D USERMOD_AUDIOREACTIVE
-D sqrt_internal=sqrtf ;; -fsingle-precision-constant ;; forces ArduinoFFT to use float math (2x faster) -D sqrt_internal=sqrtf ;; -fsingle-precision-constant ;; forces ArduinoFFT to use float math (2x faster)
AR_lib_deps = kosme/arduinoFFT @ 2.0.1 AR_lib_deps = kosme/arduinoFFT @ 2.0.1
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs
[esp32_idf_V4] [esp32_idf_V4]
;; experimental build environment for ESP32 using ESP-IDF 4.4.x / arduino-esp32 v2.0.5 ;; experimental build environment for ESP32 using ESP-IDF 4.4.x / arduino-esp32 v2.0.5
@ -270,69 +282,78 @@ AR_lib_deps = kosme/arduinoFFT @ 2.0.1
;; ;;
;; please note that you can NOT update existing ESP32 installs with a "V4" build. Also updating by OTA will not work properly. ;; please note that you can NOT update existing ESP32 installs with a "V4" build. Also updating by OTA will not work properly.
;; You need to completely erase your device (esptool erase_flash) first, then install the "V4" build from VSCode+platformio. ;; You need to completely erase your device (esptool erase_flash) first, then install the "V4" build from VSCode+platformio.
platform = espressif32@ ~6.3.2
platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0 ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them) ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them)
platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.06.02/platform-espressif32.zip ;; Tasmota Arduino Core 2.0.9 with IPv6 support, based on IDF 4.4.4
build_unflags = ${common.build_unflags}
build_flags = -g build_flags = -g
-Wshadow=compatible-local ;; emit warning in case a local variable "shadows" another local one -Wshadow=compatible-local ;; emit warning in case a local variable "shadows" another local one
-DARDUINO_ARCH_ESP32 -DESP32 -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 -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 = 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} ${env.lib_deps}
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs
[esp32s2] [esp32s2]
;; generic definitions for all ESP32-S2 boards ;; generic definitions for all ESP32-S2 boards
platform = espressif32@ ~6.3.2 platform = ${esp32_idf_V4.platform}
platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0 ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them) build_unflags = ${common.build_unflags}
build_flags = -g build_flags = -g
-DARDUINO_ARCH_ESP32 -DARDUINO_ARCH_ESP32
-DARDUINO_ARCH_ESP32S2 -DARDUINO_ARCH_ESP32S2
-DCONFIG_IDF_TARGET_ESP32S2=1 -DCONFIG_IDF_TARGET_ESP32S2=1
-D CONFIG_ASYNC_TCP_USE_WDT=0
-DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0
-DCO -DCO
-DARDUINO_USB_MODE=0 ;; this flag is mandatory for ESP32-S2 ! -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: ;; 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 ;; ARDUINO_USB_CDC_ON_BOOT
${esp32_all_variants.build_flags}
lib_deps = lib_deps =
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 ${esp32_all_variants.lib_deps}
${env.lib_deps} ${env.lib_deps}
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs
[esp32c3] [esp32c3]
;; generic definitions for all ESP32-C3 boards ;; generic definitions for all ESP32-C3 boards
platform = espressif32@ ~6.3.2 platform = ${esp32_idf_V4.platform}
platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0 ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them) build_unflags = ${common.build_unflags}
build_flags = -g build_flags = -g
-DARDUINO_ARCH_ESP32 -DARDUINO_ARCH_ESP32
-DARDUINO_ARCH_ESP32C3 -DARDUINO_ARCH_ESP32C3
-DCONFIG_IDF_TARGET_ESP32C3=1 -DCONFIG_IDF_TARGET_ESP32C3=1
-D CONFIG_ASYNC_TCP_USE_WDT=0
-DCO -DCO
-DARDUINO_USB_MODE=1 ;; this flag is mandatory for ESP32-C3 -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: ;; 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 ;; ARDUINO_USB_CDC_ON_BOOT
${esp32_all_variants.build_flags}
lib_deps = lib_deps =
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 ${esp32_all_variants.lib_deps}
${env.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] [esp32s3]
;; generic definitions for all ESP32-S3 boards ;; generic definitions for all ESP32-S3 boards
platform = espressif32@ ~6.3.2 platform = ${esp32_idf_V4.platform}
platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0 ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them) build_unflags = ${common.build_unflags}
build_flags = -g build_flags = -g
-DESP32 -DESP32
-DARDUINO_ARCH_ESP32 -DARDUINO_ARCH_ESP32
-DARDUINO_ARCH_ESP32S3 -DARDUINO_ARCH_ESP32S3
-DCONFIG_IDF_TARGET_ESP32S3=1 -DCONFIG_IDF_TARGET_ESP32S3=1
-D CONFIG_ASYNC_TCP_USE_WDT=0
-DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_DFU_ON_BOOT=0 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_DFU_ON_BOOT=0
-DCO -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: ;; 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 ;; ARDUINO_USB_MODE, ARDUINO_USB_CDC_ON_BOOT
${esp32_all_variants.build_flags}
lib_deps = lib_deps =
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 ${esp32_all_variants.lib_deps}
${env.lib_deps} ${env.lib_deps}
board_build.partitions = ${esp32.large_partitions} ;; default partioning for 8MB flash - can be overridden in build envs
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@ -345,7 +366,7 @@ platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages} platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_4m1m} board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags} build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP8266 #-DWLED_DISABLE_2D build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266\" #-DWLED_DISABLE_2D
lib_deps = ${esp8266.lib_deps} lib_deps = ${esp8266.lib_deps}
monitor_filters = esp8266_exception_decoder monitor_filters = esp8266_exception_decoder
@ -354,13 +375,13 @@ extends = env:nodemcuv2
;; using platform version and build options from WLED 0.14.0 ;; using platform version and build options from WLED 0.14.0
platform = ${esp8266.platform_compat} platform = ${esp8266.platform_compat}
platform_packages = ${esp8266.platform_packages_compat} platform_packages = ${esp8266.platform_packages_compat}
build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=ESP8266_compat #-DWLED_DISABLE_2D build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP8266_compat\" #-DWLED_DISABLE_2D
;; lib_deps = ${esp8266.lib_deps_compat} ;; experimental - use older NeoPixelBus 2.7.9 ;; lib_deps = ${esp8266.lib_deps_compat} ;; experimental - use older NeoPixelBus 2.7.9
[env:nodemcuv2_160] [env:nodemcuv2_160]
extends = env:nodemcuv2 extends = env:nodemcuv2
board_build.f_cpu = 160000000L board_build.f_cpu = 160000000L
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP8266_160 #-DWLED_DISABLE_2D build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266_160\" #-DWLED_DISABLE_2D
-D USERMOD_AUDIOREACTIVE -D USERMOD_AUDIOREACTIVE
[env:esp8266_2m] [env:esp8266_2m]
@ -369,7 +390,7 @@ platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages} platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_2m512k} board_build.ldscript = ${common.ldscript_2m512k}
build_unflags = ${common.build_unflags} build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP02 build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP02\"
lib_deps = ${esp8266.lib_deps} lib_deps = ${esp8266.lib_deps}
[env:esp8266_2m_compat] [env:esp8266_2m_compat]
@ -377,12 +398,12 @@ extends = env:esp8266_2m
;; using platform version and build options from WLED 0.14.0 ;; using platform version and build options from WLED 0.14.0
platform = ${esp8266.platform_compat} platform = ${esp8266.platform_compat}
platform_packages = ${esp8266.platform_packages_compat} platform_packages = ${esp8266.platform_packages_compat}
build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=ESP02_compat #-DWLED_DISABLE_2D build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP02_compat\" #-DWLED_DISABLE_2D
[env:esp8266_2m_160] [env:esp8266_2m_160]
extends = env:esp8266_2m extends = env:esp8266_2m
board_build.f_cpu = 160000000L board_build.f_cpu = 160000000L
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP02_160 build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP02_160\"
-D USERMOD_AUDIOREACTIVE -D USERMOD_AUDIOREACTIVE
[env:esp01_1m_full] [env:esp01_1m_full]
@ -391,7 +412,7 @@ platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages} platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_1m128k} board_build.ldscript = ${common.ldscript_1m128k}
build_unflags = ${common.build_unflags} build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP01 -D WLED_DISABLE_OTA build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP01\" -D WLED_DISABLE_OTA
; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM ; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM
lib_deps = ${esp8266.lib_deps} lib_deps = ${esp8266.lib_deps}
@ -400,12 +421,12 @@ extends = env:esp01_1m_full
;; using platform version and build options from WLED 0.14.0 ;; using platform version and build options from WLED 0.14.0
platform = ${esp8266.platform_compat} platform = ${esp8266.platform_compat}
platform_packages = ${esp8266.platform_packages_compat} platform_packages = ${esp8266.platform_packages_compat}
build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=ESP01_compat -D WLED_DISABLE_OTA #-DWLED_DISABLE_2D build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP01_compat\" -D WLED_DISABLE_OTA #-DWLED_DISABLE_2D
[env:esp01_1m_full_160] [env:esp01_1m_full_160]
extends = env:esp01_1m_full extends = env:esp01_1m_full
board_build.f_cpu = 160000000L board_build.f_cpu = 160000000L
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP01_160 -D WLED_DISABLE_OTA build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP01_160\" -D WLED_DISABLE_OTA
-D USERMOD_AUDIOREACTIVE -D USERMOD_AUDIOREACTIVE
; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM ; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM
@ -414,19 +435,30 @@ board = esp32dev
platform = ${esp32.platform} platform = ${esp32.platform}
platform_packages = ${esp32.platform_packages} platform_packages = ${esp32.platform_packages}
build_unflags = ${common.build_unflags} build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=ESP32 #-D WLED_DISABLE_BROWNOUT_DET build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32\" #-D WLED_DISABLE_BROWNOUT_DET
${esp32.AR_build_flags} ${esp32.AR_build_flags}
lib_deps = ${esp32.lib_deps} lib_deps = ${esp32.lib_deps}
${esp32.AR_lib_deps} ${esp32.AR_lib_deps}
monitor_filters = esp32_exception_decoder monitor_filters = esp32_exception_decoder
board_build.partitions = ${esp32.default_partitions} board_build.partitions = ${esp32.default_partitions}
[env:esp32dev_V4]
board = esp32dev
platform = ${esp32_idf_V4.platform}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_V4\" #-D WLED_DISABLE_BROWNOUT_DET
${esp32.AR_build_flags}
lib_deps = ${esp32_idf_V4.lib_deps}
${esp32.AR_lib_deps}
monitor_filters = esp32_exception_decoder
board_build.partitions = ${esp32.default_partitions}
board_build.flash_mode = dio
[env:esp32dev_8M] [env:esp32dev_8M]
board = esp32dev board = esp32dev
platform = ${esp32_idf_V4.platform} platform = ${esp32_idf_V4.platform}
platform_packages = ${esp32_idf_V4.platform_packages}
build_unflags = ${common.build_unflags} build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=ESP32_8M #-D WLED_DISABLE_BROWNOUT_DET build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_8M\" #-D WLED_DISABLE_BROWNOUT_DET
${esp32.AR_build_flags} ${esp32.AR_build_flags}
lib_deps = ${esp32_idf_V4.lib_deps} lib_deps = ${esp32_idf_V4.lib_deps}
${esp32.AR_lib_deps} ${esp32.AR_lib_deps}
@ -440,9 +472,8 @@ board_upload.maximum_size = 8388608
[env:esp32dev_16M] [env:esp32dev_16M]
board = esp32dev board = esp32dev
platform = ${esp32_idf_V4.platform} platform = ${esp32_idf_V4.platform}
platform_packages = ${esp32_idf_V4.platform_packages}
build_unflags = ${common.build_unflags} build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=ESP32_16M #-D WLED_DISABLE_BROWNOUT_DET build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_16M\" #-D WLED_DISABLE_BROWNOUT_DET
${esp32.AR_build_flags} ${esp32.AR_build_flags}
lib_deps = ${esp32_idf_V4.lib_deps} lib_deps = ${esp32_idf_V4.lib_deps}
${esp32.AR_lib_deps} ${esp32.AR_lib_deps}
@ -458,7 +489,7 @@ board_build.flash_mode = dio
;platform = ${esp32.platform} ;platform = ${esp32.platform}
;platform_packages = ${esp32.platform_packages} ;platform_packages = ${esp32.platform_packages}
;build_unflags = ${common.build_unflags} ;build_unflags = ${common.build_unflags}
;build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=ESP32_audioreactive #-D WLED_DISABLE_BROWNOUT_DET ;build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32_audioreactive\" #-D WLED_DISABLE_BROWNOUT_DET
; ${esp32.AR_build_flags} ; ${esp32.AR_build_flags}
;lib_deps = ${esp32.lib_deps} ;lib_deps = ${esp32.lib_deps}
; ${esp32.AR_lib_deps} ; ${esp32.AR_lib_deps}
@ -473,7 +504,7 @@ platform = ${esp32.platform}
platform_packages = ${esp32.platform_packages} platform_packages = ${esp32.platform_packages}
upload_speed = 921600 upload_speed = 921600
build_unflags = ${common.build_unflags} build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=ESP32_Ethernet -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1 build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32_Ethernet\" -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1
; -D WLED_DISABLE_ESPNOW ;; ESP-NOW requires wifi, may crash with ethernet only ; -D WLED_DISABLE_ESPNOW ;; ESP-NOW requires wifi, may crash with ethernet only
${esp32.AR_build_flags} ${esp32.AR_build_flags}
lib_deps = ${esp32.lib_deps} lib_deps = ${esp32.lib_deps}
@ -483,13 +514,12 @@ board_build.partitions = ${esp32.default_partitions}
[env:esp32_wrover] [env:esp32_wrover]
extends = esp32_idf_V4 extends = esp32_idf_V4
platform = ${esp32_idf_V4.platform} platform = ${esp32_idf_V4.platform}
platform_packages = ${esp32_idf_V4.platform_packages}
board = ttgo-t7-v14-mini32 board = ttgo-t7-v14-mini32
board_build.f_flash = 80000000L board_build.f_flash = 80000000L
board_build.flash_mode = qio board_build.flash_mode = qio
board_build.partitions = ${esp32.extended_partitions} board_build.partitions = ${esp32.extended_partitions}
build_unflags = ${common.build_unflags} build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=ESP32_WROVER build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_WROVER\"
-DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue ;; Older ESP32 (rev.<3) need a PSRAM fix (increases static RAM used) https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/external-ram.html -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue ;; Older ESP32 (rev.<3) need a PSRAM fix (increases static RAM used) https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/external-ram.html
-D DATA_PINS=25 -D DATA_PINS=25
${esp32.AR_build_flags} ${esp32.AR_build_flags}
@ -499,11 +529,10 @@ lib_deps = ${esp32_idf_V4.lib_deps}
[env:esp32c3dev] [env:esp32c3dev]
extends = esp32c3 extends = esp32c3
platform = ${esp32c3.platform} platform = ${esp32c3.platform}
platform_packages = ${esp32c3.platform_packages}
framework = arduino framework = arduino
board = esp32-c3-devkitm-1 board = esp32-c3-devkitm-1
board_build.partitions = ${esp32.default_partitions} board_build.partitions = ${esp32.default_partitions}
build_flags = ${common.build_flags} ${esp32c3.build_flags} -D WLED_RELEASE_NAME=ESP32-C3 build_flags = ${common.build_flags} ${esp32c3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-C3\"
-D WLED_WATCHDOG_TIMEOUT=0 -D WLED_WATCHDOG_TIMEOUT=0
-DLOLIN_WIFI_FIX ; seems to work much better with this -DLOLIN_WIFI_FIX ; seems to work much better with this
-DARDUINO_USB_CDC_ON_BOOT=1 ;; for virtual CDC USB -DARDUINO_USB_CDC_ON_BOOT=1 ;; for virtual CDC USB
@ -517,10 +546,9 @@ lib_deps = ${esp32c3.lib_deps}
board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support
board_build.arduino.memory_type = qio_opi ;; use with PSRAM: 8MB or 16MB board_build.arduino.memory_type = qio_opi ;; use with PSRAM: 8MB or 16MB
platform = ${esp32s3.platform} platform = ${esp32s3.platform}
platform_packages = ${esp32s3.platform_packages}
upload_speed = 921600 upload_speed = 921600
build_unflags = ${common.build_unflags} build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_16MB_opi build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_16MB_opi\"
-D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0
;-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip ;-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip
-D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
@ -540,10 +568,9 @@ monitor_filters = esp32_exception_decoder
board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support
board_build.arduino.memory_type = qio_opi ;; use with PSRAM: 8MB or 16MB board_build.arduino.memory_type = qio_opi ;; use with PSRAM: 8MB or 16MB
platform = ${esp32s3.platform} platform = ${esp32s3.platform}
platform_packages = ${esp32s3.platform_packages}
upload_speed = 921600 upload_speed = 921600
build_unflags = ${common.build_unflags} build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_8MB_opi build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_8MB_opi\"
-D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0
;-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip ;-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip
-D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
@ -560,12 +587,11 @@ monitor_filters = esp32_exception_decoder
;; For ESP32-S3 WROOM-2, a.k.a. ESP32-S3 DevKitC-1 v1.1 ;; For ESP32-S3 WROOM-2, a.k.a. ESP32-S3 DevKitC-1 v1.1
;; with >= 16MB FLASH and >= 8MB PSRAM (memory_type: opi_opi) ;; with >= 16MB FLASH and >= 8MB PSRAM (memory_type: opi_opi)
platform = ${esp32s3.platform} platform = ${esp32s3.platform}
platform_packages = ${esp32s3.platform_packages}
board = esp32s3camlcd ;; this is the only standard board with "opi_opi" board = esp32s3camlcd ;; this is the only standard board with "opi_opi"
board_build.arduino.memory_type = opi_opi board_build.arduino.memory_type = opi_opi
upload_speed = 921600 upload_speed = 921600
build_unflags = ${common.build_unflags} build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_WROOM-2 build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_WROOM-2\"
-D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0
-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip -D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip
;; -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") ;; -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
@ -587,10 +613,9 @@ monitor_filters = esp32_exception_decoder
;; ESP32-S3, with 4MB FLASH and <= 4MB PSRAM (memory_type: qio_qspi) ;; ESP32-S3, with 4MB FLASH and <= 4MB PSRAM (memory_type: qio_qspi)
board = lolin_s3_mini ;; -S3 mini, 4MB flash 2MB PSRAM board = lolin_s3_mini ;; -S3 mini, 4MB flash 2MB PSRAM
platform = ${esp32s3.platform} platform = ${esp32s3.platform}
platform_packages = ${esp32s3.platform_packages}
upload_speed = 921600 upload_speed = 921600
build_unflags = ${common.build_unflags} build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_4M_qspi build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_4M_qspi\"
-DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
-DBOARD_HAS_PSRAM -DBOARD_HAS_PSRAM
-DLOLIN_WIFI_FIX ; seems to work much better with this -DLOLIN_WIFI_FIX ; seems to work much better with this
@ -605,20 +630,18 @@ monitor_filters = esp32_exception_decoder
[env:lolin_s2_mini] [env:lolin_s2_mini]
platform = ${esp32s2.platform} platform = ${esp32s2.platform}
platform_packages = ${esp32s2.platform_packages}
board = lolin_s2_mini board = lolin_s2_mini
board_build.partitions = ${esp32.default_partitions} board_build.partitions = ${esp32.default_partitions}
board_build.flash_mode = qio board_build.flash_mode = qio
board_build.f_flash = 80000000L board_build.f_flash = 80000000L
build_unflags = ${common.build_unflags} build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME=ESP32-S2 build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S2\"
-DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_CDC_ON_BOOT=1
-DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_MSC_ON_BOOT=0
-DARDUINO_USB_DFU_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0
-DBOARD_HAS_PSRAM -DBOARD_HAS_PSRAM
-DLOLIN_WIFI_FIX ; seems to work much better with this -DLOLIN_WIFI_FIX ; seems to work much better with this
-D WLED_WATCHDOG_TIMEOUT=0 -D WLED_WATCHDOG_TIMEOUT=0
-D CONFIG_ASYNC_TCP_USE_WDT=0
-D DATA_PINS=16 -D DATA_PINS=16
-D HW_PIN_SCL=35 -D HW_PIN_SCL=35
-D HW_PIN_SDA=33 -D HW_PIN_SDA=33

View File

@ -5,7 +5,7 @@
# Please visit documentation: https://docs.platformio.org/page/projectconf.html # Please visit documentation: https://docs.platformio.org/page/projectconf.html
[platformio] [platformio]
default_envs = WLED_tasmota_1M # define as many as you need default_envs = WLED_generic8266_1M, esp32dev_V4_dio80 # put the name(s) of your own build environment here. You can define as many as you need
#---------- #----------
# SAMPLE # SAMPLE
@ -28,8 +28,8 @@ lib_deps = ${esp8266.lib_deps}
; robtillaart/SHT85@~0.3.3 ; robtillaart/SHT85@~0.3.3
; ;gmag11/QuickESPNow @ ~0.7.0 # will also load QuickDebug ; ;gmag11/QuickESPNow @ ~0.7.0 # will also load QuickDebug
; https://github.com/blazoncek/QuickESPNow.git#optional-debug ;; exludes debug library ; https://github.com/blazoncek/QuickESPNow.git#optional-debug ;; exludes debug library
; ${esp32.AR_lib_deps} ;; used for USERMOD_AUDIOREACTIVE
; bitbank2/PNGdec@^1.0.1 ;; used for POV display uncomment following ; bitbank2/PNGdec@^1.0.1 ;; used for POV display uncomment following
; ${esp32.AR_lib_deps} ;; needed for USERMOD_AUDIOREACTIVE
build_unflags = ${common.build_unflags} build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} build_flags = ${common.build_flags} ${esp8266.build_flags}
@ -37,7 +37,7 @@ build_flags = ${common.build_flags} ${esp8266.build_flags}
; *** To use the below defines/overrides, copy and paste each onto it's own line just below build_flags in the section above. ; *** To use the below defines/overrides, copy and paste each onto it's own line just below build_flags in the section above.
; ;
; Set a release name that may be used to distinguish required binary for flashing ; Set a release name that may be used to distinguish required binary for flashing
; -D WLED_RELEASE_NAME=ESP32_MULTI_USREMODS ; -D WLED_RELEASE_NAME=\"ESP32_MULTI_USREMODS\"
; ;
; disable specific features ; disable specific features
; -D WLED_DISABLE_OTA ; -D WLED_DISABLE_OTA
@ -111,7 +111,6 @@ build_flags = ${common.build_flags} ${esp8266.build_flags}
; ;
; Use 4 Line Display usermod with SPI display ; Use 4 Line Display usermod with SPI display
; -D USERMOD_FOUR_LINE_DISPLAY ; -D USERMOD_FOUR_LINE_DISPLAY
; -D USE_ALT_DISPlAY # mandatory
; -DFLD_SPI_DEFAULT ; -DFLD_SPI_DEFAULT
; -D FLD_TYPE=SSD1306_SPI64 ; -D FLD_TYPE=SSD1306_SPI64
; -D FLD_PIN_CLOCKSPI=14 ; -D FLD_PIN_CLOCKSPI=14
@ -142,7 +141,8 @@ build_flags = ${common.build_flags} ${esp8266.build_flags}
; -D PIR_SENSOR_MAX_SENSORS=2 # max allowable sensors (uses OR logic for triggering) ; -D PIR_SENSOR_MAX_SENSORS=2 # max allowable sensors (uses OR logic for triggering)
; ;
; Use Audioreactive usermod and configure I2S microphone ; Use Audioreactive usermod and configure I2S microphone
; -D USERMOD_AUDIOREACTIVE ; ${esp32.AR_build_flags} ;; default flags required to properly configure ArduinoFFT
; ;; don't forget to add ArduinoFFT to your libs_deps: ${esp32.AR_lib_deps}
; -D AUDIOPIN=-1 ; -D AUDIOPIN=-1
; -D DMTYPE=1 # 0-analog/disabled, 1-I2S generic, 2-ES7243, 3-SPH0645, 4-I2S+mclk, 5-I2S PDM ; -D DMTYPE=1 # 0-analog/disabled, 1-I2S generic, 2-ES7243, 3-SPH0645, 4-I2S+mclk, 5-I2S PDM
; -D I2S_SDPIN=36 ; -D I2S_SDPIN=36
@ -163,12 +163,17 @@ build_flags = ${common.build_flags} ${esp8266.build_flags}
; -D SERVERNAME="\"WLED\"" ; -D SERVERNAME="\"WLED\""
; ;
; set the number of LEDs ; set the number of LEDs
; -D DEFAULT_LED_COUNT=30 ; -D PIXEL_COUNTS=30
; or this for multiple outputs ; or this for multiple outputs
; -D PIXEL_COUNTS=30,30 ; -D PIXEL_COUNTS=30,30
; ;
; set the default LED type ; set the default LED type
; -D DEFAULT_LED_TYPE=22 # see const.h (TYPE_xxxx) ; -D LED_TYPES=22 # see const.h (TYPE_xxxx)
; or this for multiple outputs
; -D LED_TYPES=TYPE_SK6812_RGBW,TYPE_WS2812_RGB
;
; set default color order of your led strip
; -D DEFAULT_LED_COLOR_ORDER=COL_ORDER_GRB
; ;
; set milliampere limit when using ESP power pin (or inadequate PSU) to power LEDs ; set milliampere limit when using ESP power pin (or inadequate PSU) to power LEDs
; -D ABL_MILLIAMPS_DEFAULT=850 ; -D ABL_MILLIAMPS_DEFAULT=850
@ -177,9 +182,6 @@ build_flags = ${common.build_flags} ${esp8266.build_flags}
; enable IR by setting remote type ; enable IR by setting remote type
; -D IRTYPE=0 # 0 Remote disabled | 1 24-key RGB | 2 24-key with CT | 3 40-key blue | 4 40-key RGB | 5 21-key RGB | 6 6-key black | 7 9-key red | 8 JSON remote ; -D IRTYPE=0 # 0 Remote disabled | 1 24-key RGB | 2 24-key with CT | 3 40-key blue | 4 40-key RGB | 5 21-key RGB | 6 6-key black | 7 9-key red | 8 JSON remote
; ;
; set default color order of your led strip
; -D DEFAULT_LED_COLOR_ORDER=COL_ORDER_GRB
;
; use PSRAM on classic ESP32 rev.1 (rev.3 or above has no issues) ; use PSRAM on classic ESP32 rev.1 (rev.3 or above has no issues)
; -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue # needed only for classic ESP32 rev.1 ; -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue # needed only for classic ESP32 rev.1
; ;
@ -237,14 +239,13 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} -D DATA_PINS=1 -D WLE
lib_deps = ${esp8266.lib_deps} lib_deps = ${esp8266.lib_deps}
[env:esp32dev_qio80] [env:esp32dev_qio80]
extends = env:esp32dev # we want to extend the existing esp32dev environment (and define only updated options)
board = esp32dev board = esp32dev
platform = ${esp32.platform}
platform_packages = ${esp32.platform_packages}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32.build_flags} #-D WLED_DISABLE_BROWNOUT_DET build_flags = ${common.build_flags} ${esp32.build_flags} #-D WLED_DISABLE_BROWNOUT_DET
${esp32.AR_build_flags} ;; optional - includes USERMOD_AUDIOREACTIVE
lib_deps = ${esp32.lib_deps} lib_deps = ${esp32.lib_deps}
${esp32.AR_lib_deps} ;; needed for USERMOD_AUDIOREACTIVE
monitor_filters = esp32_exception_decoder monitor_filters = esp32_exception_decoder
board_build.partitions = ${esp32.default_partitions}
board_build.f_flash = 80000000L board_build.f_flash = 80000000L
board_build.flash_mode = qio board_build.flash_mode = qio
@ -252,26 +253,25 @@ board_build.flash_mode = qio
;; experimental ESP32 env using ESP-IDF V4.4.x ;; experimental ESP32 env using ESP-IDF V4.4.x
;; Warning: this build environment is not stable!! ;; Warning: this build environment is not stable!!
;; please erase your device before installing. ;; please erase your device before installing.
extends = esp32_idf_V4 # based on newer "esp-idf V4" platform environment
board = esp32dev board = esp32dev
platform = ${esp32_idf_V4.platform}
platform_packages = ${esp32_idf_V4.platform_packages}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} #-D WLED_DISABLE_BROWNOUT_DET build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} #-D WLED_DISABLE_BROWNOUT_DET
${esp32.AR_build_flags} ;; includes USERMOD_AUDIOREACTIVE
lib_deps = ${esp32_idf_V4.lib_deps} lib_deps = ${esp32_idf_V4.lib_deps}
${esp32.AR_lib_deps} ;; needed for USERMOD_AUDIOREACTIVE
monitor_filters = esp32_exception_decoder monitor_filters = esp32_exception_decoder
board_build.partitions = ${esp32_idf_V4.default_partitions} board_build.partitions = ${esp32.default_partitions} ;; if you get errors about "out of program space", change this to ${esp32.extended_partitions} or even ${esp32.big_partitions}
board_build.f_flash = 80000000L board_build.f_flash = 80000000L
board_build.flash_mode = dio board_build.flash_mode = dio
[env:esp32s2_saola] [env:esp32s2_saola]
extends = esp32s2
board = esp32-s2-saola-1 board = esp32-s2-saola-1
platform = ${esp32s2.platform} platform = ${esp32s2.platform}
platform_packages = ${esp32s2.platform_packages} platform_packages = ${esp32s2.platform_packages}
framework = arduino framework = arduino
board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
board_build.flash_mode = qio board_build.flash_mode = qio
upload_speed = 460800 upload_speed = 460800
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32s2.build_flags} build_flags = ${common.build_flags} ${esp32s2.build_flags}
;-DLOLIN_WIFI_FIX ;; try this in case Wifi does not work ;-DLOLIN_WIFI_FIX ;; try this in case Wifi does not work
-DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_CDC_ON_BOOT=1
@ -308,7 +308,7 @@ platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages} platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_4m1m} board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags} build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_USE_SHOJO_PCB build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_USE_SHOJO_PCB ;; NB: WLED_USE_SHOJO_PCB is not used anywhere in the source code. Not sure why its needed.
lib_deps = ${esp8266.lib_deps} lib_deps = ${esp8266.lib_deps}
[env:d1_mini_debug] [env:d1_mini_debug]
@ -363,36 +363,48 @@ board_upload.flash_size = 2MB
board_upload.maximum_size = 2097152 board_upload.maximum_size = 2097152
[env:wemos_shield_esp32] [env:wemos_shield_esp32]
extends = esp32 ;; use default esp32 platform
board = esp32dev board = esp32dev
platform = ${esp32.platform}
platform_packages = ${esp32.platform_packages}
upload_speed = 460800 upload_speed = 460800
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32.build_flags} build_flags = ${common.build_flags} ${esp32.build_flags}
-D WLED_RELEASE_NAME=\"ESP32_wemos_shield\"
-D DATA_PINS=16 -D DATA_PINS=16
-D RLYPIN=19 -D RLYPIN=19
-D BTNPIN=17 -D BTNPIN=17
-D IRPIN=18 -D IRPIN=18
-D UWLED_USE_MY_CONFIG -UWLED_USE_MY_CONFIG
-D USERMOD_DALLASTEMPERATURE -D USERMOD_DALLASTEMPERATURE
-D USERMOD_FOUR_LINE_DISPLAY -D USERMOD_FOUR_LINE_DISPLAY
-D TEMPERATURE_PIN=23 -D TEMPERATURE_PIN=23
-D USE_ALT_DISPlAY ; new versions of USERMOD_FOUR_LINE_DISPLAY and USERMOD_ROTARY_ENCODER_UI ${esp32.AR_build_flags} ;; includes USERMOD_AUDIOREACTIVE
-D USERMOD_AUDIOREACTIVE
lib_deps = ${esp32.lib_deps} lib_deps = ${esp32.lib_deps}
OneWire@~2.3.5 OneWire@~2.3.5 ;; needed for USERMOD_DALLASTEMPERATURE
olikraus/U8g2 @ ^2.28.8 olikraus/U8g2 @ ^2.28.8 ;; needed for USERMOD_FOUR_LINE_DISPLAY
https://github.com/blazoncek/arduinoFFT.git ${esp32.AR_lib_deps} ;; needed for USERMOD_AUDIOREACTIVE
board_build.partitions = ${esp32.default_partitions} board_build.partitions = ${esp32.default_partitions}
[env:m5atom] [env:esp32_pico-D4]
board = esp32dev extends = esp32 ;; use default esp32 platform
build_unflags = ${common.build_unflags} board = pico32 ;; pico32-D4 is different from the standard esp32dev
build_flags = ${common.build_flags} ${esp32.build_flags} -D DATA_PINS=27 -D BTNPIN=39 ;; hardware details from https://github.com/srg74/WLED-ESP32-pico
build_flags = ${common.build_flags} ${esp32.build_flags}
-D WLED_RELEASE_NAME=\"pico32-D4\" -D SERVERNAME='"WLED-pico32"'
-D WLED_DISABLE_ADALIGHT ;; no serial-to-USB chip on this board - better to disable serial protocols
-D DATA_PINS=2,18 ;; LED pins
-D RLYPIN=19 -D BTNPIN=0 -D IRPIN=-1 ;; no default pin for IR
${esp32.AR_build_flags} ;; include USERMOD_AUDIOREACTIVE
-D UM_AUDIOREACTIVE_ENABLE ;; enable AR by default
;; Audioreactive settings for on-board microphone (ICS-43432)
-D SR_DMTYPE=1 -D I2S_SDPIN=25 -D I2S_WSPIN=15 -D I2S_CKPIN=14
-D SR_SQUELCH=5 -D SR_GAIN=30
lib_deps = ${esp32.lib_deps} lib_deps = ${esp32.lib_deps}
platform = ${esp32.platform} ${esp32.AR_lib_deps} ;; needed for USERMOD_AUDIOREACTIVE
platform_packages = ${esp32.platform_packages}
board_build.partitions = ${esp32.default_partitions} board_build.partitions = ${esp32.default_partitions}
board_build.f_flash = 80000000L
[env:m5atom]
extends = env:esp32dev # we want to extend the existing esp32dev environment (and define only updated options)
build_flags = ${common.build_flags} ${esp32.build_flags} -D DATA_PINS=27 -D BTNPIN=39
[env:sp501e] [env:sp501e]
board = esp_wroom_02 board = esp_wroom_02
@ -415,7 +427,7 @@ platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_2m512k} board_build.ldscript = ${common.ldscript_2m512k}
build_unflags = ${common.build_unflags} build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D BTNPIN=-1 -D RLYPIN=-1 -D DATA_PINS=4,12,14,13,5 build_flags = ${common.build_flags} ${esp8266.build_flags} -D BTNPIN=-1 -D RLYPIN=-1 -D DATA_PINS=4,12,14,13,5
-D DEFAULT_LED_TYPE=TYPE_ANALOG_5CH -D WLED_DISABLE_INFRARED -D WLED_MAX_CCT_BLEND=0 -D LED_TYPES=TYPE_ANALOG_5CH -D WLED_DISABLE_INFRARED -D WLED_MAX_CCT_BLEND=0
lib_deps = ${esp8266.lib_deps} lib_deps = ${esp8266.lib_deps}
[env:Athom_15w_RGBCW] ;15w bulb [env:Athom_15w_RGBCW] ;15w bulb
@ -425,7 +437,7 @@ platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_2m512k} board_build.ldscript = ${common.ldscript_2m512k}
build_unflags = ${common.build_unflags} build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D BTNPIN=-1 -D RLYPIN=-1 -D DATA_PINS=4,12,14,5,13 build_flags = ${common.build_flags} ${esp8266.build_flags} -D BTNPIN=-1 -D RLYPIN=-1 -D DATA_PINS=4,12,14,5,13
-D DEFAULT_LED_TYPE=TYPE_ANALOG_5CH -D WLED_DISABLE_INFRARED -D WLED_MAX_CCT_BLEND=0 -D WLED_USE_IC_CCT -D LED_TYPES=TYPE_ANALOG_5CH -D WLED_DISABLE_INFRARED -D WLED_MAX_CCT_BLEND=0 -D WLED_USE_IC_CCT
lib_deps = ${esp8266.lib_deps} lib_deps = ${esp8266.lib_deps}
[env:Athom_3Pin_Controller] ;small controller with only data [env:Athom_3Pin_Controller] ;small controller with only data
@ -491,9 +503,8 @@ lib_deps = ${esp8266.lib_deps}
# EleksTube-IPS # EleksTube-IPS
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
[env:elekstube_ips] [env:elekstube_ips]
extends = esp32 ;; use default esp32 platform
board = esp32dev board = esp32dev
platform = ${esp32.platform}
platform_packages = ${esp32.platform_packages}
upload_speed = 921600 upload_speed = 921600
build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_DISABLE_BROWNOUT_DET -D WLED_DISABLE_INFRARED build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_DISABLE_BROWNOUT_DET -D WLED_DISABLE_INFRARED
-D USERMOD_RTC -D USERMOD_RTC
@ -501,7 +512,7 @@ build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_DISABLE_BROWNOU
-D DATA_PINS=12 -D DATA_PINS=12
-D RLYPIN=27 -D RLYPIN=27
-D BTNPIN=34 -D BTNPIN=34
-D DEFAULT_LED_COUNT=6 -D PIXEL_COUNTS=6
# Display config # Display config
-D ST7789_DRIVER -D ST7789_DRIVER
-D TFT_WIDTH=135 -D TFT_WIDTH=135
@ -517,5 +528,15 @@ build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_DISABLE_BROWNOU
monitor_filters = esp32_exception_decoder monitor_filters = esp32_exception_decoder
lib_deps = lib_deps =
${esp32.lib_deps} ${esp32.lib_deps}
TFT_eSPI @ ^2.3.70 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
board_build.partitions = ${esp32.default_partitions}
# ------------------------------------------------------------------------------
# Usermod examples
# ------------------------------------------------------------------------------
# 433MHz RF remote example for esp32dev
[env:esp32dev_usermod_RF433]
extends = env:esp32dev
build_flags = ${env:esp32dev.build_flags} -D USERMOD_RF433
lib_deps = ${env:esp32dev.lib_deps}
sui77/rc-switch @ 2.6.4

View File

@ -12,7 +12,7 @@
# Welcome to my project WLED! ✨ # Welcome to my project WLED! ✨
A fast and feature-rich implementation of an ESP8266/ESP32 webserver to control NeoPixel (WS2812B, WS2811, SK6812) LEDs or also SPI based chipsets like the WS2801 and APA102! A fast and feature-rich implementation of an ESP32 and ESP8266 webserver to control NeoPixel (WS2812B, WS2811, SK6812) LEDs or also SPI based chipsets like the WS2801 and APA102!
## ⚙️ Features ## ⚙️ Features
- WS2812FX library with more than 100 special effects - WS2812FX library with more than 100 special effects
@ -21,7 +21,7 @@ A fast and feature-rich implementation of an ESP8266/ESP32 webserver to control
- Segments to set different effects and colors to user defined parts of the LED string - Segments to set different effects and colors to user defined parts of the LED string
- Settings page - configuration via the network - Settings page - configuration via the network
- Access Point and station mode - automatic failsafe AP - Access Point and station mode - automatic failsafe AP
- Up to 10 LED outputs per instance - [Up to 10 LED outputs](https://kno.wled.ge/features/multi-strip/#esp32) per instance
- Support for RGBW strips - Support for RGBW strips
- Up to 250 user presets to save and load colors/effects easily, supports cycling through them. - Up to 250 user presets to save and load colors/effects easily, supports cycling through them.
- Presets can be used to automatically execute API calls - Presets can be used to automatically execute API calls

View File

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

BIN
tools/AutoCubeMap.xlsx Normal file

Binary file not shown.

View File

@ -101,6 +101,7 @@ function adoptVersionAndRepo(html) {
async function minify(str, type = "plain") { async function minify(str, type = "plain") {
const options = { const options = {
collapseWhitespace: true, collapseWhitespace: true,
conservativeCollapse: true, // preserve spaces in text
collapseBooleanAttributes: true, collapseBooleanAttributes: true,
collapseInlineTagWhitespace: true, collapseInlineTagWhitespace: true,
minifyCSS: true, minifyCSS: true,

View File

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

View File

@ -102,9 +102,9 @@ private:
void secondsEffectSineFade(int16_t secondLed, Toki::Time const& time) { void secondsEffectSineFade(int16_t secondLed, Toki::Time const& time) {
uint32_t ms = time.ms % 1000; uint32_t ms = time.ms % 1000;
uint8_t b0 = (cos8(ms * 64 / 1000) - 128) * 2; uint8_t b0 = (cos8_t(ms * 64 / 1000) - 128) * 2;
setPixelColor(secondLed, gamma32(scale32(secondColor, b0))); setPixelColor(secondLed, gamma32(scale32(secondColor, b0)));
uint8_t b1 = (sin8(ms * 64 / 1000) - 128) * 2; uint8_t b1 = (sin8_t(ms * 64 / 1000) - 128) * 2;
setPixelColor(inc(secondLed, 1, secondsSegment), gamma32(scale32(secondColor, b1))); setPixelColor(inc(secondLed, 1, secondsSegment), gamma32(scale32(secondColor, b1)));
} }

View File

@ -50,7 +50,7 @@ public:
#else // ESP32 ESP32S3 and ESP32C3 #else // ESP32 ESP32S3 and ESP32C3
temperature = roundf(temperatureRead() * 10) / 10; temperature = roundf(temperatureRead() * 10) / 10;
#endif #endif
if(presetToActivate != 0){
// Check if temperature has exceeded the activation threshold // Check if temperature has exceeded the activation threshold
if (temperature >= activationThreshold) { if (temperature >= activationThreshold) {
// Update the state flag if not already set // Update the state flag if not already set
@ -58,7 +58,7 @@ public:
isAboveThreshold = true; isAboveThreshold = true;
} }
// Check if a 'high temperature' preset is configured and it's not already active // Check if a 'high temperature' preset is configured and it's not already active
if (presetToActivate != 0 && currentPreset != presetToActivate) { if (currentPreset != presetToActivate) {
// If a playlist is active, store it for reactivation later // If a playlist is active, store it for reactivation later
if (currentPlaylist > 0) { if (currentPlaylist > 0) {
previousPlaylist = currentPlaylist; previousPlaylist = currentPlaylist;
@ -101,6 +101,7 @@ public:
} }
} }
} }
}
#ifndef WLED_DISABLE_MQTT #ifndef WLED_DISABLE_MQTT
if (WLED_MQTT_CONNECTED) if (WLED_MQTT_CONNECTED)

View File

@ -75,7 +75,7 @@ static uint8_t soundAgc = 0; // Automagic gain control: 0 - n
//static float volumeSmth = 0.0f; // either sampleAvg or sampleAgc depending on soundAgc; smoothed sample //static float volumeSmth = 0.0f; // either sampleAvg or sampleAgc depending on soundAgc; smoothed sample
static float FFT_MajorPeak = 1.0f; // FFT: strongest (peak) frequency static float FFT_MajorPeak = 1.0f; // FFT: strongest (peak) frequency
static float FFT_Magnitude = 0.0f; // FFT: volume (magnitude) of peak frequency static float FFT_Magnitude = 0.0f; // FFT: volume (magnitude) of peak frequency
static bool samplePeak = false; // Boolean flag for peak - used in effects. Responding routine may reset this flag. Auto-reset after strip.getMinShowDelay() static bool samplePeak = false; // Boolean flag for peak - used in effects. Responding routine may reset this flag. Auto-reset after strip.getFrameTime()
static bool udpSamplePeak = false; // Boolean flag for peak. Set at the same time as samplePeak, but reset by transmitAudioData static bool udpSamplePeak = false; // Boolean flag for peak. Set at the same time as samplePeak, but reset by transmitAudioData
static unsigned long timeOfPeak = 0; // time of last sample peak detection. static unsigned long timeOfPeak = 0; // time of last sample peak detection.
static uint8_t fftResult[NUM_GEQ_CHANNELS]= {0};// Our calculated freq. channel result table to be used by effects static uint8_t fftResult[NUM_GEQ_CHANNELS]= {0};// Our calculated freq. channel result table to be used by effects
@ -536,8 +536,8 @@ static void detectSamplePeak(void) {
#endif #endif
static void autoResetPeak(void) { static void autoResetPeak(void) {
uint16_t MinShowDelay = MAX(50, strip.getMinShowDelay()); // Fixes private class variable compiler error. Unsure if this is the correct way of fixing the root problem. -THATDONFC uint16_t peakDelay = max(uint16_t(50), strip.getFrameTime());
if (millis() - timeOfPeak > MinShowDelay) { // Auto-reset of samplePeak after a complete frame has passed. if (millis() - timeOfPeak > peakDelay) { // Auto-reset of samplePeak after at least one complete frame has passed.
samplePeak = false; samplePeak = false;
if (audioSyncEnabled == 0) udpSamplePeak = false; // this is normally reset by transmitAudioData if (audioSyncEnabled == 0) udpSamplePeak = false; // this is normally reset by transmitAudioData
} }

View File

@ -0,0 +1,84 @@
# Deep Sleep usermod
This usermod unleashes the low power capabilities of th ESP: when you power off your LEDs (using the UI power button or a macro) the ESP will be put into deep sleep mode, reducing power consumption to a minimum.
During deep sleep the ESP is shut down completely: no WiFi, no CPU, no outputs. The only way to wake it up is to use an external signal or a button. Once it wakes from deep sleep it reboots so ***make sure to use a boot-up preset.***
# A word of warning
When you disable the WLED option 'Turn LEDs on after power up/reset' and 'DelaySleep' is set to zero the ESP will go into deep sleep directly after power-up and only start WLED after it has been woken up.
If the ESP can not be awoken from deep sleep due to a wrong configuration it has to be factory reset, disabling sleep at power-up. There is no other way to wake it up.
# Power Consumption in deep sleep
The current drawn by the ESP in deep sleep mode depends on the type and is in the range of 5uA-20uA (as in micro Amperes):
- ESP32: 10uA
- ESP32 S3: 8uA
- ESP32 S2: 20uA
- ESP32 C3: 5uA
- ESP8266: 20uA (not supported in this usermod)
However, there is usually additional components on a controller that increase the value:
- Power LED: the power LED on a ESP board draws 500uA - 1mA
- LDO: the voltage regulator also draws idle current. Depending on the type used this can be around 50uA up to 10mA (LM1117). Special low power LDOs with very low idle currents do exist
- Digital LEDs: WS2812 for example draw a current of about 1mA per LED. To make good use of this usermod it is required to power them off using MOSFETs or a Relay
For lowest power consumption, remove the Power LED and make sure your board does not use an LM1117. On a ESP32 C3 Supermini with the power LED removed (no other modifications) powered through the 5V pin I measured a current draw of 50uA in deep sleep.
# Useable GPIOs
The GPIOs that can be used to wake the ESP from deep sleep are limited. Only pins connected to the internal RTC unit can be used:
- ESP32: GPIO 0, 2, 4, 12-15, 25-39
- ESP32 S3: GPIO 0-21
- ESP32 S2: GPIO 0-21
- ESP32 C3: GPIO 0-5
- ESP8266 is not supported in this usermod
You can however use the selected wake-up pin normally in WLED, it only gets activated as a wake-up pin when your LEDs are powered down.
# Limitations
To keep this usermod simple and easy to use, it is a very basic implementation of the low-power capabilities provided by the ESP. If you need more advanced control you are welcome to implement your own version based on this usermod.
## Usermod installation
Use `#define USERMOD_DEEP_SLEEP` in wled.h or `-D USERMOD_DEEP_SLEEP` in your platformio.ini. Settings can be changed in the usermod config UI.
### Define Settings
There are five parameters you can set:
- GPIO: the pin to use for wake-up
- WakeWhen High/Low: the pin state that triggers the wake-up
- Pull-up/down disable: enable or disable the internal pullup resistors during sleep (does not affect normal use while running)
- Wake after: if set larger than 0, ESP will automatically wake-up after this many seconds (Turn LEDs on after power up/reset is overriden, it will always turn on)
- Delay sleep: if set larger than 0, ESP will not go to sleep for this many seconds after you power it off. Timer is reset when switched back on during this time.
To override the default settings, place the `#define` in wled.h or add `-D DEEPSLEEP_xxx` to your platformio_override.ini build flags
* `DEEPSLEEP_WAKEUPPIN x` - define the pin to be used for wake-up, see list of useable pins above. The pin can be used normally as a button pin in WLED.
* `DEEPSLEEP_WAKEWHENHIGH` - if defined, wakes up when pin goes high (default is low)
* `DEEPSLEEP_DISABLEPULL` - if defined, internal pullup/pulldown is disabled in deep sleep (default is ebnabled)
* `DEEPSLEEP_WAKEUPINTERVAL` - number of seconds after which a wake-up happens automatically, sooner if button is pressed. 0 = never. accuracy is about 2%
* `DEEPSLEEP_DELAY` - delay between power-off and sleep
example for env build flags:
`-D USERMOD_DEEP_SLEEP`
`-D DEEPSLEEP_WAKEUPPIN=4`
`-D DEEPSLEEP_DISABLEPULL=0` ;enable pull-up/down resistors by default
`-D DEEPSLEEP_WAKEUPINTERVAL=43200` ;wake up after 12 hours (or when button is pressed)
### Hardware Setup
To wake from deep-sleep an external trigger signal on the configured GPIO is required. When using timed-only wake-up, use a GPIO that has an on-board pull-up resistor (GPIO0 on most boards). When using push-buttons it is highly recommended to use an external pull-up resistor: not all IO's on all devices have properly working internal resistors.
Using sensors like PIR, IR, touch sensors or any other sensor with a digital output can be used instead of a button.
now go on and save some power
@dedehai
## Change log
2024-09
* Initial version
2024-10
* Changed from #define configuration to UI configuration

View File

@ -0,0 +1,227 @@
#pragma once
#include "wled.h"
#include "driver/rtc_io.h"
#ifdef ESP8266
#error The "Deep Sleep" usermod does not support ESP8266
#endif
#ifndef DEEPSLEEP_WAKEUPPIN
#define DEEPSLEEP_WAKEUPPIN 0
#endif
#ifndef DEEPSLEEP_WAKEWHENHIGH
#define DEEPSLEEP_WAKEWHENHIGH 0
#endif
#ifndef DEEPSLEEP_DISABLEPULL
#define DEEPSLEEP_DISABLEPULL 1
#endif
#ifndef DEEPSLEEP_WAKEUPINTERVAL
#define DEEPSLEEP_WAKEUPINTERVAL 0
#endif
#ifndef DEEPSLEEP_DELAY
#define DEEPSLEEP_DELAY 1
#endif
RTC_DATA_ATTR bool powerup = true; // variable in RTC data persists on a reboot
class DeepSleepUsermod : public Usermod {
private:
bool enabled = true;
bool initDone = false;
uint8_t wakeupPin = DEEPSLEEP_WAKEUPPIN;
uint8_t wakeWhenHigh = DEEPSLEEP_WAKEWHENHIGH; // wake up when pin goes high if 1, triggers on low if 0
bool noPull = true; // use pullup/pulldown resistor
int wakeupAfter = DEEPSLEEP_WAKEUPINTERVAL; // in seconds, <=0: button only
int sleepDelay = DEEPSLEEP_DELAY; // in seconds, 0 = immediate
int delaycounter = 5; // delay deep sleep at bootup until preset settings are applied
uint32_t lastLoopTime = 0;
// string that are used multiple time (this will save some flash memory)
static const char _name[];
static const char _enabled[];
bool pin_is_valid(uint8_t wakePin) {
#ifdef CONFIG_IDF_TARGET_ESP32 //ESP32: GPIOs 0,2,4, 12-15, 25-39 can be used for wake-up
if (wakePin == 0 || wakePin == 2 || wakePin == 4 || (wakePin >= 12 && wakePin <= 15) || (wakePin >= 25 && wakePin <= 27) || (wakePin >= 32 && wakePin <= 39)) {
return true;
}
#endif
#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32S2) //ESP32 S3 & S3: GPIOs 0-21 can be used for wake-up
if (wakePin <= 21) {
return true;
}
#endif
#ifdef CONFIG_IDF_TARGET_ESP32C3 // ESP32 C3: GPIOs 0-5 can be used for wake-up
if (wakePin <= 5) {
return true;
}
#endif
DEBUG_PRINTLN(F("Error: unsupported deep sleep wake-up pin"));
return false;
}
public:
inline void enable(bool enable) { enabled = enable; } // Enable/Disable the usermod
inline bool isEnabled() { return enabled; } //Get usermod enabled/disabled state
// setup is called at boot (or in this case after every exit of sleep mode)
void setup() {
//TODO: if the de-init of RTC pins is required to do it could be done here
//rtc_gpio_deinit(wakeupPin);
initDone = true;
}
void loop() {
if (!enabled || !offMode) { // disabled or LEDs are on
lastLoopTime = 0; // reset timer
return;
}
if (sleepDelay > 0) {
if(lastLoopTime == 0) lastLoopTime = millis(); // initialize
if (millis() - lastLoopTime < sleepDelay * 1000) {
return; // wait until delay is over
}
}
if(powerup == false && delaycounter) { // delay sleep in case a preset is being loaded and turnOnAtBoot is disabled (handleIO() does enable offMode temporarily in this case)
delaycounter--;
if(delaycounter == 2 && offMode) { // force turn on, no matter the settings (device is bricked if user set sleepDelay=0, no bootup preset and turnOnAtBoot=false)
if (briS == 0) bri = 10; // turn on at low brightness
else bri = briS;
strip.setBrightness(bri); // needed to make handleIO() not turn off LEDs (really? does not help in bootup preset)
offMode = false;
applyPresetWithFallback(0, CALL_MODE_INIT, FX_MODE_STATIC, 0); // try to apply preset 0, fallback to static
if (rlyPin >= 0) {
digitalWrite(rlyPin, (rlyMde ? HIGH : LOW)); // turn relay on TODO: this should be done by wled, what function to call?
}
}
return;
}
DEBUG_PRINTLN(F("DeepSleep UM: entering deep sleep..."));
powerup = false; // turn leds on in all subsequent bootups (overrides Turn LEDs on after power up/reset' at reboot)
if(!pin_is_valid(wakeupPin)) return;
esp_err_t halerror = ESP_OK;
pinMode(wakeupPin, INPUT); // make sure GPIO is input with pullup/pulldown disabled
esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL); //disable all wake-up sources (just in case)
if(wakeupAfter)
esp_sleep_enable_timer_wakeup((uint64_t)wakeupAfter * (uint64_t)1e6); //sleep for x seconds
#if defined(CONFIG_IDF_TARGET_ESP32C3) // ESP32 C3
if(noPull)
gpio_sleep_set_pull_mode((gpio_num_t)wakeupPin, GPIO_FLOATING);
else { // enable pullup/pulldown resistor
if(wakeWhenHigh)
gpio_sleep_set_pull_mode((gpio_num_t)wakeupPin, GPIO_PULLDOWN_ONLY);
else
gpio_sleep_set_pull_mode((gpio_num_t)wakeupPin, GPIO_PULLUP_ONLY);
}
if(wakeWhenHigh)
halerror = esp_deep_sleep_enable_gpio_wakeup(1<<wakeupPin, ESP_GPIO_WAKEUP_GPIO_HIGH);
else
halerror = esp_deep_sleep_enable_gpio_wakeup(1<<wakeupPin, ESP_GPIO_WAKEUP_GPIO_LOW);
#else // ESP32, S2, S3
gpio_pulldown_dis((gpio_num_t)wakeupPin); // disable internal pull resistors for GPIO use
gpio_pullup_dis((gpio_num_t)wakeupPin);
if(noPull) {
rtc_gpio_pullup_dis((gpio_num_t)wakeupPin);
rtc_gpio_pulldown_dis((gpio_num_t)wakeupPin);
}
else { // enable pullup/pulldown resistor for RTC use
if(wakeWhenHigh)
rtc_gpio_pulldown_en((gpio_num_t)wakeupPin);
else
rtc_gpio_pullup_en((gpio_num_t)wakeupPin);
}
if(wakeWhenHigh)
halerror = esp_sleep_enable_ext0_wakeup((gpio_num_t)wakeupPin, HIGH); // only RTC pins can be used
else
halerror = esp_sleep_enable_ext0_wakeup((gpio_num_t)wakeupPin, LOW);
#endif
delay(1); // wait for pin to be ready
if(halerror == ESP_OK) esp_deep_sleep_start(); // go into deep sleep
else DEBUG_PRINTLN(F("sleep failed"));
}
//void connected() {} //unused, this is called every time the WiFi is (re)connected
void addToConfig(JsonObject& root) override
{
JsonObject top = root.createNestedObject(FPSTR(_name));
top[FPSTR(_enabled)] = enabled;
//save these vars persistently whenever settings are saved
top["gpio"] = wakeupPin;
top["wakeWhen"] = wakeWhenHigh;
top["pull"] = noPull;
top["wakeAfter"] = wakeupAfter;
top["delaySleep"] = sleepDelay;
}
bool readFromConfig(JsonObject& root) override
{
// default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor
// setting them inside readFromConfig() is slightly more robust, handling the rare but plausible use case of single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed)
JsonObject top = root[FPSTR(_name)];
bool configComplete = !top.isNull();
configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled);
configComplete &= getJsonValue(top["gpio"], wakeupPin, DEEPSLEEP_WAKEUPPIN);
if (!pin_is_valid(wakeupPin)) {
wakeupPin = 0; // set to 0 if invalid
configComplete = false; // Mark config as incomplete if pin is invalid
}
configComplete &= getJsonValue(top["wakeWhen"], wakeWhenHigh, DEEPSLEEP_WAKEWHENHIGH); // default to wake on low
configComplete &= getJsonValue(top["pull"], noPull, DEEPSLEEP_DISABLEPULL); // default to no pullup/pulldown
configComplete &= getJsonValue(top["wakeAfter"], wakeupAfter, DEEPSLEEP_WAKEUPINTERVAL);
configComplete &= getJsonValue(top["delaySleep"], sleepDelay, DEEPSLEEP_DELAY);
return configComplete;
}
/*
* appendConfigData() is called when user enters usermod settings page
* it may add additional metadata for certain entry fields (adding drop down is possible)
* be careful not to add too much as oappend() buffer is limited to 3k
*/
void appendConfigData() override
{
// dropdown for wakeupPin
oappend(SET_F("dd=addDropdown('DeepSleep','gpio');"));
for (int pin = 0; pin < 40; pin++) { // possible pins are in range 0-39
if (pin_is_valid(pin)) {
oappend(SET_F("addOption(dd,'"));
oappend(String(pin).c_str());
oappend(SET_F("',"));
oappend(String(pin).c_str());
oappend(SET_F(");"));
}
}
oappend(SET_F("dd=addDropdown('DeepSleep','wakeWhen');"));
oappend(SET_F("addOption(dd,'Low',0);"));
oappend(SET_F("addOption(dd,'High',1);"));
oappend(SET_F("addInfo('DeepSleep:pull',1,'','-up/down disable: ');")); // first string is suffix, second string is prefix
oappend(SET_F("addInfo('DeepSleep:wakeAfter',1,'seconds <i>(0 = never)<i>');"));
oappend(SET_F("addInfo('DeepSleep:delaySleep',1,'seconds <i>(0 = sleep at powerup)<i>');")); // first string is suffix, second string is prefix
}
/*
* 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_DEEP_SLEEP;
}
};
// add more strings here to reduce flash memory usage
const char DeepSleepUsermod::_name[] PROGMEM = "DeepSleep";
const char DeepSleepUsermod::_enabled[] PROGMEM = "enabled";

View File

@ -9,7 +9,7 @@ The actual / original code that controls the LED modes is from Adam Zeloof. I ta
It was quite a bit more work than I hoped, but I got there eventually :) It was quite a bit more work than I hoped, but I got there eventually :)
## Requirements ## Requirements
* "ESP Rotary" by Lennart Hennigs, v1.5.0 or higher: https://github.com/LennartHennigs/ESPRotary * "ESP Rotary" by Lennart Hennigs, v2.1.1 or higher: https://github.com/LennartHennigs/ESPRotary
## Usermod installation ## Usermod installation
Simply copy the below block (build task) to your `platformio_override.ini` and compile WLED using this new build task. Or use an existing one and add the buildflag `-D RGB_ROTARY_ENCODER`. Simply copy the below block (build task) to your `platformio_override.ini` and compile WLED using this new build task. Or use an existing one and add the buildflag `-D RGB_ROTARY_ENCODER`.
@ -20,7 +20,7 @@ ESP32:
extends = env:esp32dev extends = env:esp32dev
build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32 -D RGB_ROTARY_ENCODER build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32 -D RGB_ROTARY_ENCODER
lib_deps = ${esp32.lib_deps} lib_deps = ${esp32.lib_deps}
lennarthennigs/ESP Rotary@^1.5.0 lennarthennigs/ESP Rotary@^2.1.1
``` ```
ESP8266 / D1 Mini: ESP8266 / D1 Mini:
@ -29,7 +29,7 @@ ESP8266 / D1 Mini:
extends = env:d1_mini extends = env:d1_mini
build_flags = ${common.build_flags_esp8266} -D RGB_ROTARY_ENCODER build_flags = ${common.build_flags_esp8266} -D RGB_ROTARY_ENCODER
lib_deps = ${esp8266.lib_deps} lib_deps = ${esp8266.lib_deps}
lennarthennigs/ESP Rotary@^1.5.0 lennarthennigs/ESP Rotary@^2.1.1
``` ```
## How to connect the board to your ESP ## How to connect the board to your ESP

View File

@ -9,7 +9,7 @@ Very loosely based on the existing usermod "seven segment display".
Add the compile-time option `-D USERMOD_SSDR` to your `platformio.ini` (or `platformio_override.ini`) or use `#define USERMOD_SSDR` in `my_config.h`. 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 ## Settings
All settings can be controlled via the usermod settings page. 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`) Shows the leading zero of the hour if it exists (i.e. shows `07` instead of `7`)
### enable-auto-brightness ### 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 ### 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 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. WLED current protection will override the calculated value if it is too high.

View File

@ -97,6 +97,11 @@ private:
#else #else
void* ptr = nullptr; void* ptr = nullptr;
#endif #endif
#ifdef USERMOD_BH1750
Usermod_BH1750* bh1750 = nullptr;
#else
void* bh1750 = nullptr;
#endif
void _overlaySevenSegmentDraw() { void _overlaySevenSegmentDraw() {
int displayMaskLen = static_cast<int>(umSSDRDisplayMask.length()); int displayMaskLen = static_cast<int>(umSSDRDisplayMask.length());
@ -387,6 +392,9 @@ public:
#ifdef USERMOD_SN_PHOTORESISTOR #ifdef USERMOD_SN_PHOTORESISTOR
ptr = (Usermod_SN_Photoresistor*) UsermodManager::lookup(USERMOD_ID_SN_PHOTORESISTOR); ptr = (Usermod_SN_Photoresistor*) UsermodManager::lookup(USERMOD_ID_SN_PHOTORESISTOR);
#endif #endif
#ifdef USERMOD_BH1750
bh1750 = (Usermod_BH1750*) UsermodManager::lookup(USERMOD_ID_BH1750);
#endif
DEBUG_PRINTLN(F("Setup done")); DEBUG_PRINTLN(F("Setup done"));
} }
@ -410,6 +418,20 @@ public:
umSSDRLastRefresh = millis(); umSSDRLastRefresh = millis();
} }
#endif #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() { void handleOverlayDraw() {

View File

@ -49,7 +49,7 @@ private:
void setColor(int r, int g, int b) void setColor(int r, int g, int b)
{ {
strip.setColor(0, r, g, b); strip.getMainSegment().setColor(0, RGBW32(r, g, b, 0));
stateUpdated(CALL_MODE_DIRECT_CHANGE); stateUpdated(CALL_MODE_DIRECT_CHANGE);
char msg[18] {}; char msg[18] {};
sprintf(msg, "rgb(%d,%d,%d)", r, g, b); sprintf(msg, "rgb(%d,%d,%d)", r, g, b);

View File

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

View File

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

View File

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

View File

@ -0,0 +1,183 @@
#pragma once
#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";

View File

@ -7,11 +7,12 @@ platform = ${esp32.platform}
build_unflags = ${common.build_unflags} build_unflags = ${common.build_unflags}
build_flags = build_flags =
${common.build_flags_esp32} ${common.build_flags_esp32}
-D USERMOD_FOUR_LINE_DISPLAY -D USE_ALT_DISPlAY -D USERMOD_FOUR_LINE_DISPLAY
-D USERMOD_ROTARY_ENCODER_UI -D ENCODER_DT_PIN=18 -D ENCODER_CLK_PIN=5 -D ENCODER_SW_PIN=19 -D FLD_TYPE=SH1106
upload_speed = 460800 -D I2CSCLPIN=27
-D I2CSDAPIN=26
lib_deps = lib_deps =
${esp32.lib_deps} ${esp32.lib_deps}
U8g2@~2.34.4 U8g2@~2.34.4
Wire Wire

View File

@ -1,16 +1,8 @@
# I2C/SPI 4 Line Display Usermod ALT # I2C/SPI 4 Line Display Usermod ALT
Thank you to the authors of the original version of these usermods. It would not have been possible without them! This usermod could be used in compination with `usermod_v2_rotary_encoder_ui_ALT`.
"usermod_v2_four_line_display"
"usermod_v2_rotary_encoder_ui"
The core of these usermods are a copy of the originals. The main changes are to the FourLineDisplay usermod. ## Functionalities
The display usermod UI has been completely changed.
The changes made to the RotaryEncoder usermod were made to support the new UI in the display usermod.
Without the display, it functions identical to the original.
The original "usermod_v2_auto_save" will not work with the display just yet.
Press the encoder to cycle through the options: Press the encoder to cycle through the options:
* Brightness * Brightness
@ -18,26 +10,18 @@ Press the encoder to cycle through the options:
* Intensity * Intensity
* Palette * Palette
* Effect * Effect
* Main Color (only if display is used) * Main Color
* Saturation (only if display is used) * Saturation
Press and hold the encoder to display Network Info. If AP is active, it will display AP, SSID and password Press and hold the encoder to display Network Info. If AP is active, it will display the AP, SSID and Password
Also shows if the timer is enabled Also shows if the timer is enabled.
[See the pair of usermods in action](https://www.youtube.com/watch?v=ulZnBt9z3TI) [See the pair of usermods in action](https://www.youtube.com/watch?v=ulZnBt9z3TI)
## Installation ## Installation
Please refer to the original `usermod_v2_rotary_encoder_ui` readme for the main instructions. Copy the example `platformio_override.sample.ini` to the root directory of your particular build.
Copy the example `platformio_override.sample.ini` from the usermod_v2_rotary_encoder_ui_ALT folder to the root directory of your particular build and rename it to `platformio_override.ini`.
This file should be placed in the same directory as `platformio.ini`.
Then, to activate this alternative usermod, add `#define USE_ALT_DISPlAY` (NOTE: CASE SENSITIVE) to the `usermods_list.cpp` file,
or add `-D USE_ALT_DISPlAY` to the original `platformio_override.ini.sample` file
## Configuration ## Configuration

View File

@ -0,0 +1,14 @@
[platformio]
default_envs = esp32dev
[env:esp32dev]
board = esp32dev
platform = ${esp32.platform}
build_unflags = ${common.build_unflags}
build_flags =
${common.build_flags_esp32}
-D USERMOD_ROTARY_ENCODER_UI
-D USERMOD_ROTARY_ENCODER_GPIO=INPUT
-D ENCODER_DT_PIN=21
-D ENCODER_CLK_PIN=23
-D ENCODER_SW_PIN=0

View File

@ -1,16 +1,8 @@
# Rotary Encoder UI Usermod ALT # Rotary Encoder UI Usermod ALT
Thank you to the authors of the original version of these usermods. It would not have been possible without them! This usermod supports the UI of the `usermod_v2_rotary_encoder_ui_ALT`.
"usermod_v2_four_line_display"
"usermod_v2_rotary_encoder_ui"
The core of these usermods are a copy of the originals. The main changes are to the FourLineDisplay usermod. ## Functionalities
The display usermod UI has been completely changed.
The changes made to the RotaryEncoder usermod were made to support the new UI in the display usermod.
Without the display, it functions identical to the original.
The original "usermod_v2_auto_save" will not work with the display just yet.
Press the encoder to cycle through the options: Press the encoder to cycle through the options:
* Brightness * Brightness
@ -21,8 +13,7 @@ Press the encoder to cycle through the options:
* Main Color (only if display is used) * Main Color (only if display is used)
* Saturation (only if display is used) * Saturation (only if display is used)
Press and hold the encoder to display Network Info Press and hold the encoder to display Network Info. If AP is active, it will display the AP, SSID and Password
if AP is active, it will display the AP, SSID and Password
Also shows if the timer is enabled. Also shows if the timer is enabled.
@ -30,9 +21,7 @@ Also shows if the timer is enabled.
## Installation ## Installation
Copy the example `platformio_override.sample.ini` to the root directory of your particular build and rename it to `platformio_override.ini`. Copy the example `platformio_override.sample.ini` to the root directory of your particular build.
To activate this alternative usermod, add `#define USE_ALT_DISPlAY` (NOTE: CASE SENSITIVE) to the `usermods_list.cpp` file, or add `-D USE_ALT_DISPlAY` to your `platformio_override.ini` file
### Define Your Options ### Define Your Options
@ -40,7 +29,6 @@ To activate this alternative usermod, add `#define USE_ALT_DISPlAY` (NOTE: CASE
* `USERMOD_FOUR_LINE_DISPLAY` - define this to have this the Four Line Display mod included wled00\usermods_list.cpp * `USERMOD_FOUR_LINE_DISPLAY` - define this to have this the Four Line Display mod included wled00\usermods_list.cpp
also tells this usermod that the display is available also tells this usermod that the display is available
(see the Four Line Display usermod `readme.md` for more details) (see the Four Line Display usermod `readme.md` for more details)
* `USE_ALT_DISPlAY` - Mandatory to use Four Line Display
* `ENCODER_DT_PIN` - defaults to 18 * `ENCODER_DT_PIN` - defaults to 18
* `ENCODER_CLK_PIN` - defaults to 5 * `ENCODER_CLK_PIN` - defaults to 5
* `ENCODER_SW_PIN` - defaults to 19 * `ENCODER_SW_PIN` - defaults to 19
@ -50,7 +38,7 @@ To activate this alternative usermod, add `#define USE_ALT_DISPlAY` (NOTE: CASE
### PlatformIO requirements ### PlatformIO requirements
Note: the Four Line Display usermod requires the libraries `U8g2` and `Wire`. No special requirements.
## Change Log ## Change Log

View File

@ -31,14 +31,14 @@ public:
//strip.getSegment(1).setOption(SEG_OPTION_SELECTED, true); //strip.getSegment(1).setOption(SEG_OPTION_SELECTED, true);
//select first two segments (background color + FX settable) //select first two segments (background color + FX settable)
WS2812FX::Segment &seg = strip.getSegment(0); Segment &seg = strip.getSegment(0);
seg.colors[0] = ((0 << 24) | ((0 & 0xFF) << 16) | ((0 & 0xFF) << 8) | ((0 & 0xFF))); seg.colors[0] = ((0 << 24) | ((0 & 0xFF) << 16) | ((0 & 0xFF) << 8) | ((0 & 0xFF)));
strip.getSegment(0).setOption(0, false); strip.getSegment(0).setOption(0, false);
strip.getSegment(0).setOption(2, false); strip.getSegment(0).setOption(2, false);
//other segments are text //other segments are text
for (int i = 1; i < 10; i++) for (int i = 1; i < 10; i++)
{ {
WS2812FX::Segment &seg = strip.getSegment(i); Segment &seg = strip.getSegment(i);
seg.colors[0] = ((0 << 24) | ((0 & 0xFF) << 16) | ((190 & 0xFF) << 8) | ((180 & 0xFF))); seg.colors[0] = ((0 << 24) | ((0 & 0xFF) << 16) | ((190 & 0xFF) << 8) | ((180 & 0xFF)));
strip.getSegment(i).setOption(0, true); strip.getSegment(i).setOption(0, true);
strip.setBrightness(64); strip.setBrightness(64);
@ -80,61 +80,61 @@ public:
void displayTime(byte hour, byte minute) void displayTime(byte hour, byte minute)
{ {
bool isToHour = false; //true if minute > 30 bool isToHour = false; //true if minute > 30
strip.setSegment(0, 0, 64); // background strip.getSegment(0).setGeometry(0, 64); // background
strip.setSegment(1, 0, 2); //It is strip.getSegment(1).setGeometry(0, 2); //It is
strip.setSegment(2, 0, 0); strip.getSegment(2).setGeometry(0, 0);
strip.setSegment(3, 0, 0); //disable minutes strip.getSegment(3).setGeometry(0, 0); //disable minutes
strip.setSegment(4, 0, 0); //past strip.getSegment(4).setGeometry(0, 0); //past
strip.setSegment(6, 0, 0); //to strip.getSegment(6).setGeometry(0, 0); //to
strip.setSegment(8, 0, 0); //disable o'clock strip.getSegment(8).setGeometry(0, 0); //disable o'clock
if (hour < 24) //valid time, display if (hour < 24) //valid time, display
{ {
if (minute == 30) if (minute == 30)
{ {
strip.setSegment(2, 3, 6); //half strip.getSegment(2).setGeometry(3, 6); //half
strip.setSegment(3, 0, 0); //minutes strip.getSegment(3).setGeometry(0, 0); //minutes
} }
else if (minute == 15 || minute == 45) else if (minute == 15 || minute == 45)
{ {
strip.setSegment(3, 0, 0); //minutes strip.getSegment(3).setGeometry(0, 0); //minutes
} }
else if (minute == 10) else if (minute == 10)
{ {
//strip.setSegment(5, 6, 8); //ten //strip.getSegment(5).setGeometry(6, 8); //ten
} }
else if (minute == 5) else if (minute == 5)
{ {
//strip.setSegment(5, 16, 18); //five //strip.getSegment(5).setGeometry(16, 18); //five
} }
else if (minute == 0) else if (minute == 0)
{ {
strip.setSegment(3, 0, 0); //minutes strip.getSegment(3).setGeometry(0, 0); //minutes
//hourChime(); //hourChime();
} }
else else
{ {
strip.setSegment(3, 18, 22); //minutes strip.getSegment(3).setGeometry(18, 22); //minutes
} }
//past or to? //past or to?
if (minute == 0) if (minute == 0)
{ //full hour { //full hour
strip.setSegment(3, 0, 0); //disable minutes strip.getSegment(3).setGeometry(0, 0); //disable minutes
strip.setSegment(4, 0, 0); //disable past strip.getSegment(4).setGeometry(0, 0); //disable past
strip.setSegment(6, 0, 0); //disable to strip.getSegment(6).setGeometry(0, 0); //disable to
strip.setSegment(8, 60, 64); //o'clock strip.getSegment(8).setGeometry(60, 64); //o'clock
} }
else if (minute > 34) else if (minute > 34)
{ {
//strip.setSegment(6, 22, 24); //to //strip.getSegment(6).setGeometry(22, 24); //to
//minute = 60 - minute; //minute = 60 - minute;
isToHour = true; isToHour = true;
} }
else else
{ {
//strip.setSegment(4, 24, 27); //past //strip.getSegment(4).setGeometry(24, 27); //past
//isToHour = false; //isToHour = false;
} }
} }
@ -143,68 +143,68 @@ public:
if (minute <= 4) if (minute <= 4)
{ {
strip.setSegment(3, 0, 0); //nothing strip.getSegment(3).setGeometry(0, 0); //nothing
strip.setSegment(5, 0, 0); //nothing strip.getSegment(5).setGeometry(0, 0); //nothing
strip.setSegment(6, 0, 0); //nothing strip.getSegment(6).setGeometry(0, 0); //nothing
strip.setSegment(8, 60, 64); //o'clock strip.getSegment(8).setGeometry(60, 64); //o'clock
} }
else if (minute <= 9) else if (minute <= 9)
{ {
strip.setSegment(5, 16, 18); // five past strip.getSegment(5).setGeometry(16, 18); // five past
strip.setSegment(4, 24, 27); //past strip.getSegment(4).setGeometry(24, 27); //past
} }
else if (minute <= 14) else if (minute <= 14)
{ {
strip.setSegment(5, 6, 8); // ten past strip.getSegment(5).setGeometry(6, 8); // ten past
strip.setSegment(4, 24, 27); //past strip.getSegment(4).setGeometry(24, 27); //past
} }
else if (minute <= 19) else if (minute <= 19)
{ {
strip.setSegment(5, 8, 12); // quarter past strip.getSegment(5).setGeometry(8, 12); // quarter past
strip.setSegment(3, 0, 0); //minutes strip.getSegment(3).setGeometry(0, 0); //minutes
strip.setSegment(4, 24, 27); //past strip.getSegment(4).setGeometry(24, 27); //past
} }
else if (minute <= 24) else if (minute <= 24)
{ {
strip.setSegment(5, 12, 16); // twenty past strip.getSegment(5).setGeometry(12, 16); // twenty past
strip.setSegment(4, 24, 27); //past strip.getSegment(4).setGeometry(24, 27); //past
} }
else if (minute <= 29) else if (minute <= 29)
{ {
strip.setSegment(5, 12, 18); // twenty-five past strip.getSegment(5).setGeometry(12, 18); // twenty-five past
strip.setSegment(4, 24, 27); //past strip.getSegment(4).setGeometry(24, 27); //past
} }
else if (minute <= 34) else if (minute <= 34)
{ {
strip.setSegment(5, 3, 6); // half past strip.getSegment(5).setGeometry(3, 6); // half past
strip.setSegment(3, 0, 0); //minutes strip.getSegment(3).setGeometry(0, 0); //minutes
strip.setSegment(4, 24, 27); //past strip.getSegment(4).setGeometry(24, 27); //past
} }
else if (minute <= 39) else if (minute <= 39)
{ {
strip.setSegment(5, 12, 18); // twenty-five to strip.getSegment(5).setGeometry(12, 18); // twenty-five to
strip.setSegment(6, 22, 24); //to strip.getSegment(6).setGeometry(22, 24); //to
} }
else if (minute <= 44) else if (minute <= 44)
{ {
strip.setSegment(5, 12, 16); // twenty to strip.getSegment(5).setGeometry(12, 16); // twenty to
strip.setSegment(6, 22, 24); //to strip.getSegment(6).setGeometry(22, 24); //to
} }
else if (minute <= 49) else if (minute <= 49)
{ {
strip.setSegment(5, 8, 12); // quarter to strip.getSegment(5).setGeometry(8, 12); // quarter to
strip.setSegment(3, 0, 0); //minutes strip.getSegment(3).setGeometry(0, 0); //minutes
strip.setSegment(6, 22, 24); //to strip.getSegment(6).setGeometry(22, 24); //to
} }
else if (minute <= 54) else if (minute <= 54)
{ {
strip.setSegment(5, 6, 8); // ten to strip.getSegment(5).setGeometry(6, 8); // ten to
strip.setSegment(6, 22, 24); //to strip.getSegment(6).setGeometry(22, 24); //to
} }
else if (minute <= 59) else if (minute <= 59)
{ {
strip.setSegment(5, 16, 18); // five to strip.getSegment(5).setGeometry(16, 18); // five to
strip.setSegment(6, 22, 24); //to strip.getSegment(6).setGeometry(22, 24); //to
} }
//hours //hours
@ -220,45 +220,45 @@ public:
switch (hour) switch (hour)
{ {
case 1: case 1:
strip.setSegment(7, 27, 29); strip.getSegment(7).setGeometry(27, 29);
break; //one break; //one
case 2: case 2:
strip.setSegment(7, 35, 37); strip.getSegment(7).setGeometry(35, 37);
break; //two break; //two
case 3: case 3:
strip.setSegment(7, 29, 32); strip.getSegment(7).setGeometry(29, 32);
break; //three break; //three
case 4: case 4:
strip.setSegment(7, 32, 35); strip.getSegment(7).setGeometry(32, 35);
break; //four break; //four
case 5: case 5:
strip.setSegment(7, 37, 40); strip.getSegment(7).setGeometry(37, 40);
break; //five break; //five
case 6: case 6:
strip.setSegment(7, 43, 45); strip.getSegment(7).setGeometry(43, 45);
break; //six break; //six
case 7: case 7:
strip.setSegment(7, 40, 43); strip.getSegment(7).setGeometry(40, 43);
break; //seven break; //seven
case 8: case 8:
strip.setSegment(7, 45, 48); strip.getSegment(7).setGeometry(45, 48);
break; //eight break; //eight
case 9: case 9:
strip.setSegment(7, 48, 50); strip.getSegment(7).setGeometry(48, 50);
break; //nine break; //nine
case 10: case 10:
strip.setSegment(7, 54, 56); strip.getSegment(7).setGeometry(54, 56);
break; //ten break; //ten
case 11: case 11:
strip.setSegment(7, 50, 54); strip.getSegment(7).setGeometry(50, 54);
break; //eleven break; //eleven
case 12: case 12:
strip.setSegment(7, 56, 60); strip.getSegment(7).setGeometry(56, 60);
break; //twelve break; //twelve
} }
selectWordSegments(true); selectWordSegments(true);
applyMacro(1); applyPreset(1);
} }
void timeOfDay() void timeOfDay()

View File

@ -1,305 +0,0 @@
#include "wled.h"
/*
* This v1 usermod file allows you to add own functionality to WLED more easily
* See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality
* EEPROM bytes 2750+ are reserved for your custom use case. (if you extend #define EEPSIZE in const.h)
* If you just need 8 bytes, use 2551-2559 (you do not need to increase EEPSIZE)
*
* Consider the v2 usermod API if you need a more advanced feature set!
*/
uint8_t minuteLast = 99;
int dayBrightness = 128;
int nightBrightness = 16;
//Use userVar0 and userVar1 (API calls &U0=,&U1=, uint16_t)
//gets called once at boot. Do all initialization that doesn't depend on network here
void userSetup()
{
saveMacro(14, "A=128", false);
saveMacro(15, "A=64", false);
saveMacro(16, "A=16", false);
saveMacro(1, "&FX=0&R=255&G=255&B=255", false);
//strip.getSegment(1).setOption(SEG_OPTION_SELECTED, true);
//select first two segments (background color + FX settable)
Segment &seg = strip.getSegment(0);
seg.colors[0] = ((0 << 24) | ((0 & 0xFF) << 16) | ((0 & 0xFF) << 8) | ((0 & 0xFF)));
strip.getSegment(0).setOption(0, false);
strip.getSegment(0).setOption(2, false);
//other segments are text
for (int i = 1; i < 10; i++)
{
Segment &seg = strip.getSegment(i);
seg.colors[0] = ((0 << 24) | ((0 & 0xFF) << 16) | ((190 & 0xFF) << 8) | ((180 & 0xFF)));
strip.getSegment(i).setOption(0, true);
strip.setBrightness(128);
}
}
//gets called every time WiFi is (re-)connected. Initialize own network interfaces here
void userConnected()
{
}
void selectWordSegments(bool state)
{
for (int i = 1; i < 10; i++)
{
//Segment &seg = strip.getSegment(i);
strip.getSegment(i).setOption(0, state);
// strip.getSegment(1).setOption(SEG_OPTION_SELECTED, true);
//seg.mode = 12;
//seg.palette = 1;
//strip.setBrightness(255);
}
strip.getSegment(0).setOption(0, !state);
}
void hourChime()
{
//strip.resetSegments();
selectWordSegments(true);
colorUpdated(CALL_MODE_FX_CHANGED);
//savePreset(255);
selectWordSegments(false);
//strip.getSegment(0).setOption(0, true);
strip.getSegment(0).setOption(2, true);
applyPreset(12);
colorUpdated(CALL_MODE_FX_CHANGED);
}
void displayTime(byte hour, byte minute)
{
bool isToHour = false; //true if minute > 30
strip.setSegment(0, 0, 64); // background
strip.setSegment(1, 0, 2); //It is
strip.setSegment(2, 0, 0);
strip.setSegment(3, 0, 0); //disable minutes
strip.setSegment(4, 0, 0); //past
strip.setSegment(6, 0, 0); //to
strip.setSegment(8, 0, 0); //disable o'clock
if (hour < 24) //valid time, display
{
if (minute == 30)
{
strip.setSegment(2, 3, 6); //half
strip.setSegment(3, 0, 0); //minutes
}
else if (minute == 15 || minute == 45)
{
strip.setSegment(3, 0, 0); //minutes
}
else if (minute == 10)
{
//strip.setSegment(5, 6, 8); //ten
}
else if (minute == 5)
{
//strip.setSegment(5, 16, 18); //five
}
else if (minute == 0)
{
strip.setSegment(3, 0, 0); //minutes
//hourChime();
}
else
{
strip.setSegment(3, 18, 22); //minutes
}
//past or to?
if (minute == 0)
{ //full hour
strip.setSegment(3, 0, 0); //disable minutes
strip.setSegment(4, 0, 0); //disable past
strip.setSegment(6, 0, 0); //disable to
strip.setSegment(8, 60, 64); //o'clock
}
else if (minute > 34)
{
//strip.setSegment(6, 22, 24); //to
//minute = 60 - minute;
isToHour = true;
}
else
{
//strip.setSegment(4, 24, 27); //past
//isToHour = false;
}
}
else
{ //temperature display
}
//byte minuteRem = minute %10;
if (minute <= 4)
{
strip.setSegment(3, 0, 0); //nothing
strip.setSegment(5, 0, 0); //nothing
strip.setSegment(6, 0, 0); //nothing
strip.setSegment(8, 60, 64); //o'clock
}
else if (minute <= 9)
{
strip.setSegment(5, 16, 18); // five past
strip.setSegment(4, 24, 27); //past
}
else if (minute <= 14)
{
strip.setSegment(5, 6, 8); // ten past
strip.setSegment(4, 24, 27); //past
}
else if (minute <= 19)
{
strip.setSegment(5, 8, 12); // quarter past
strip.setSegment(3, 0, 0); //minutes
strip.setSegment(4, 24, 27); //past
}
else if (minute <= 24)
{
strip.setSegment(5, 12, 16); // twenty past
strip.setSegment(4, 24, 27); //past
}
else if (minute <= 29)
{
strip.setSegment(5, 12, 18); // twenty-five past
strip.setSegment(4, 24, 27); //past
}
else if (minute <= 34)
{
strip.setSegment(5, 3, 6); // half past
strip.setSegment(3, 0, 0); //minutes
strip.setSegment(4, 24, 27); //past
}
else if (minute <= 39)
{
strip.setSegment(5, 12, 18); // twenty-five to
strip.setSegment(6, 22, 24); //to
}
else if (minute <= 44)
{
strip.setSegment(5, 12, 16); // twenty to
strip.setSegment(6, 22, 24); //to
}
else if (minute <= 49)
{
strip.setSegment(5, 8, 12); // quarter to
strip.setSegment(3, 0, 0); //minutes
strip.setSegment(6, 22, 24); //to
}
else if (minute <= 54)
{
strip.setSegment(5, 6, 8); // ten to
strip.setSegment(6, 22, 24); //to
}
else if (minute <= 59)
{
strip.setSegment(5, 16, 18); // five to
strip.setSegment(6, 22, 24); //to
}
//hours
if (hour > 23)
return;
if (isToHour)
hour++;
if (hour > 12)
hour -= 12;
if (hour == 0)
hour = 12;
switch (hour)
{
case 1:
strip.setSegment(7, 27, 29);
break; //one
case 2:
strip.setSegment(7, 35, 37);
break; //two
case 3:
strip.setSegment(7, 29, 32);
break; //three
case 4:
strip.setSegment(7, 32, 35);
break; //four
case 5:
strip.setSegment(7, 37, 40);
break; //five
case 6:
strip.setSegment(7, 43, 45);
break; //six
case 7:
strip.setSegment(7, 40, 43);
break; //seven
case 8:
strip.setSegment(7, 45, 48);
break; //eight
case 9:
strip.setSegment(7, 48, 50);
break; //nine
case 10:
strip.setSegment(7, 54, 56);
break; //ten
case 11:
strip.setSegment(7, 50, 54);
break; //eleven
case 12:
strip.setSegment(7, 56, 60);
break; //twelve
}
selectWordSegments(true);
applyMacro(1);
}
void timeOfDay() {
// NOT USED: use timed macros instead
//Used to set brightness dependant of time of day - lights dimmed at night
//monday to thursday and sunday
if ((weekday(localTime) == 6) | (weekday(localTime) == 7)) {
if (hour(localTime) > 0 | hour(localTime) < 8) {
strip.setBrightness(nightBrightness);
}
else {
strip.setBrightness(dayBrightness);
}
}
else {
if (hour(localTime) < 6 | hour(localTime) >= 22) {
strip.setBrightness(nightBrightness);
}
else {
strip.setBrightness(dayBrightness);
}
}
}
//loop. You can use "if (WLED_CONNECTED)" to check for successful connection
void userLoop()
{
if (minute(localTime) != minuteLast)
{
updateLocalTime();
//timeOfDay();
minuteLast = minute(localTime);
displayTime(hour(localTime), minute(localTime));
if (minute(localTime) == 0){
hourChime();
}
if (minute(localTime) == 1){
//turn off background segment;
strip.getSegment(0).setOption(2, false);
//applyPreset(255);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -16,6 +16,7 @@
#include <vector> #include <vector>
#include "const.h" #include "const.h"
#include "bus_manager.h"
#define FASTLED_INTERNAL //remove annoying pragma messages #define FASTLED_INTERNAL //remove annoying pragma messages
#define USE_GET_MILLISECOND_TIMER #define USE_GET_MILLISECOND_TIMER
@ -42,10 +43,21 @@
#define RGBW32(r,g,b,w) (uint32_t((byte(w) << 24) | (byte(r) << 16) | (byte(g) << 8) | (byte(b)))) #define RGBW32(r,g,b,w) (uint32_t((byte(w) << 24) | (byte(r) << 16) | (byte(g) << 8) | (byte(b))))
#endif #endif
extern bool realtimeRespectLedMaps; // used in getMappedPixelIndex()
extern byte realtimeMode; // used in getMappedPixelIndex()
/* Not used in all effects yet */ /* Not used in all effects yet */
#define WLED_FPS 42 #define WLED_FPS 42
#define FRAMETIME_FIXED (1000/WLED_FPS) #define FRAMETIME_FIXED (1000/WLED_FPS)
#define FRAMETIME strip.getFrameTime() #define FRAMETIME strip.getFrameTime()
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S2)
#define MIN_FRAME_DELAY 2 // minimum wait between repaints, to keep other functions like WiFi alive
#elif defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3)
#define MIN_FRAME_DELAY 3 // S2/C3 are slower than normal esp32, and only have one core
#else
#define MIN_FRAME_DELAY 8 // 8266 legacy MIN_SHOW_DELAY
#endif
#define FPS_UNLIMITED 0
// FPS calculation (can be defined as compile flag for debugging) // FPS calculation (can be defined as compile flag for debugging)
#ifndef FPS_CALC_AVG #ifndef FPS_CALC_AVG
@ -67,9 +79,9 @@
#define MAX_NUM_SEGMENTS 32 #define MAX_NUM_SEGMENTS 32
#endif #endif
#if defined(ARDUINO_ARCH_ESP32S2) #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 #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
#endif #endif
@ -77,16 +89,14 @@
assuming each segment uses the same amount of data. 256 for ESP8266, 640 for ESP32. */ assuming each segment uses the same amount of data. 256 for ESP8266, 640 for ESP32. */
#define FAIR_DATA_PER_SEG (MAX_SEGMENT_DATA / strip.getMaxSegments()) #define FAIR_DATA_PER_SEG (MAX_SEGMENT_DATA / strip.getMaxSegments())
#define MIN_SHOW_DELAY (_frametime < 16 ? 8 : 15)
#define NUM_COLORS 3 /* number of colors per segment */ #define NUM_COLORS 3 /* number of colors per segment */
#define SEGMENT strip._segments[strip.getCurrSegmentId()] #define SEGMENT strip._segments[strip.getCurrSegmentId()]
#define SEGENV strip._segments[strip.getCurrSegmentId()] #define SEGENV strip._segments[strip.getCurrSegmentId()]
//#define SEGCOLOR(x) strip._segments[strip.getCurrSegmentId()].currentColor(x, strip._segments[strip.getCurrSegmentId()].colors[x]) #define SEGCOLOR(x) Segment::getCurrentColor(x)
//#define SEGLEN strip._segments[strip.getCurrSegmentId()].virtualLength()
#define SEGCOLOR(x) strip.segColor(x) /* saves us a few kbytes of code */
#define SEGPALETTE Segment::getCurrentPalette() #define SEGPALETTE Segment::getCurrentPalette()
#define SEGLEN strip._virtualSegmentLength /* saves us a few kbytes of code */ #define SEGLEN Segment::vLength()
#define SEG_W Segment::vWidth()
#define SEG_H Segment::vHeight()
#define SPEED_FORMULA_L (5U + (50U*(255U - SEGMENT.speed))/SEGLEN) #define SPEED_FORMULA_L (5U + (50U*(255U - SEGMENT.speed))/SEGLEN)
// some common colors // some common colors
@ -174,7 +184,7 @@
#define FX_MODE_TWO_DOTS 50 #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_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_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_CHASE 54
#define FX_MODE_TRICOLOR_WIPE 55 #define FX_MODE_TRICOLOR_WIPE 55
#define FX_MODE_TRICOLOR_FADE 56 #define FX_MODE_TRICOLOR_FADE 56
@ -198,7 +208,7 @@
#define FX_MODE_COLORTWINKLE 74 #define FX_MODE_COLORTWINKLE 74
#define FX_MODE_LAKE 75 #define FX_MODE_LAKE 75
#define FX_MODE_METEOR 76 #define FX_MODE_METEOR 76
#define FX_MODE_METEOR_SMOOTH 77 //#define FX_MODE_METEOR_SMOOTH 77 // merged with meteor
#define FX_MODE_RAILWAY 78 #define FX_MODE_RAILWAY 78
#define FX_MODE_RIPPLE 79 #define FX_MODE_RIPPLE 79
#define FX_MODE_TWINKLEFOX 80 #define FX_MODE_TWINKLEFOX 80
@ -315,6 +325,30 @@
#define MODE_COUNT 187 #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 { typedef enum mapping1D2D {
M12_Pixels = 0, M12_Pixels = 0,
M12_pBar = 1, M12_pBar = 1,
@ -323,7 +357,7 @@ typedef enum mapping1D2D {
M12_sPinwheel = 4 M12_sPinwheel = 4
} mapping1D2D_t; } mapping1D2D_t;
// segment, 80 bytes // segment, 68 bytes
typedef struct Segment { typedef struct Segment {
public: public:
uint16_t start; // start index / start X coordinate 2D (left) uint16_t start; // start index / start X coordinate 2D (left)
@ -363,6 +397,7 @@ typedef struct Segment {
}; };
uint8_t startY; // start Y coodrinate 2D (top); there should be no more than 255 rows uint8_t startY; // start Y coodrinate 2D (top); there should be no more than 255 rows
uint8_t stopY; // stop Y coordinate 2D (bottom); there should be no more than 255 rows uint8_t stopY; // stop Y coordinate 2D (bottom); there should be no more than 255 rows
// note: two bytes of padding are added here
char *name; char *name;
// runtime data // runtime data
@ -391,7 +426,7 @@ typedef struct Segment {
uint32_t _stepT; uint32_t _stepT;
uint32_t _callT; uint32_t _callT;
uint8_t *_dataT; uint8_t *_dataT;
uint16_t _dataLenT; unsigned _dataLenT;
TemporarySegmentData() TemporarySegmentData()
: _dataT(nullptr) // just in case... : _dataT(nullptr) // just in case...
, _dataLenT(0) , _dataLenT(0)
@ -409,17 +444,25 @@ typedef struct Segment {
uint8_t _reserved : 4; uint8_t _reserved : 4;
}; };
}; };
uint16_t _dataLen; uint8_t _default_palette; // palette number that gets assigned to pal0
static uint16_t _usedSegmentData; unsigned _dataLen;
static unsigned _usedSegmentData;
// perhaps this should be per segment, not static static uint8_t _segBri; // brightness of segment for current effect
static unsigned _vLength; // 1D dimension used for current effect
static unsigned _vWidth, _vHeight; // 2D dimensions used for current effect
static uint32_t _currentColors[NUM_COLORS]; // colors used for current effect
static bool _colorScaled; // color has been scaled prior to setPixelColor() call
static CRGBPalette16 _currentPalette; // palette used for current effect (includes transition, used in color_from_palette()) static CRGBPalette16 _currentPalette; // palette used for current effect (includes transition, used in color_from_palette())
static CRGBPalette16 _randomPalette; // actual random palette static CRGBPalette16 _randomPalette; // actual random palette
static CRGBPalette16 _newRandomPalette; // target random palette static CRGBPalette16 _newRandomPalette; // target random palette
static uint16_t _lastPaletteChange; // last random palette change time in millis()/1000 static uint16_t _lastPaletteChange; // last random palette change time in millis()/1000
static uint16_t _lastPaletteBlend; // blend palette according to set Transition Delay in millis()%0xFFFF static uint16_t _lastPaletteBlend; // blend palette according to set Transition Delay in millis()%0xFFFF
static uint16_t _transitionprogress; // current transition progress 0 - 0xFFFF
#ifndef WLED_DISABLE_MODE_BLEND #ifndef WLED_DISABLE_MODE_BLEND
static bool _modeBlend; // mode/effect blending semaphore static bool _modeBlend; // mode/effect blending semaphore
// clipping
static uint16_t _clipStart, _clipStop;
static uint8_t _clipStartY, _clipStopY;
#endif #endif
// transition data, valid only if transitional==true, holds values during transition (72 bytes) // transition data, valid only if transitional==true, holds values during transition (72 bytes)
@ -430,6 +473,7 @@ typedef struct Segment {
#else #else
uint32_t _colorT[NUM_COLORS]; uint32_t _colorT[NUM_COLORS];
#endif #endif
uint8_t _palTid; // previous palette
uint8_t _briT; // temporary brightness uint8_t _briT; // temporary brightness
uint8_t _cctT; // temporary CCT uint8_t _cctT; // temporary CCT
CRGBPalette16 _palT; // temporary palette CRGBPalette16 _palT; // temporary palette
@ -444,6 +488,8 @@ typedef struct Segment {
{} {}
} *_t; } *_t;
[[gnu::hot]] void _setPixelColorXY_raw(const int& x, const int& y, uint32_t& col) const; // set pixel without mapping (internal use only)
public: public:
Segment(uint16_t sStart=0, uint16_t sStop=30) : Segment(uint16_t sStart=0, uint16_t sStop=30) :
@ -476,6 +522,7 @@ typedef struct Segment {
aux1(0), aux1(0),
data(nullptr), data(nullptr),
_capabilities(0), _capabilities(0),
_default_palette(0),
_dataLen(0), _dataLen(0),
_t(nullptr) _t(nullptr)
{ {
@ -499,7 +546,7 @@ typedef struct Segment {
//if (data) Serial.printf(" %d->(%p)", (int)_dataLen, data); //if (data) Serial.printf(" %d->(%p)", (int)_dataLen, data);
//Serial.println(); //Serial.println();
#endif #endif
if (name) { delete[] name; name = nullptr; } if (name) { free(name); name = nullptr; }
stopTransition(); stopTransition();
deallocateData(); deallocateData();
} }
@ -515,7 +562,6 @@ typedef struct Segment {
inline bool isSelected() const { return selected; } inline bool isSelected() const { return selected; }
inline bool isInTransition() const { return _t != nullptr; } inline bool isInTransition() const { return _t != nullptr; }
inline bool isActive() const { return stop > start; } inline bool isActive() const { return stop > start; }
inline bool is2D() const { return (width()>1 && height()>1); }
inline bool hasRGB() const { return _isRGB; } inline bool hasRGB() const { return _isRGB; }
inline bool hasWhite() const { return _hasW; } inline bool hasWhite() const { return _hasW; }
inline bool isCCT() const { return _isCCT; } inline bool isCCT() const { return _isCCT; }
@ -524,23 +570,30 @@ typedef struct Segment {
inline uint16_t length() const { return width() * height(); } // segment length (count) in physical pixels inline uint16_t length() const { return width() * height(); } // segment length (count) in physical pixels
inline uint16_t groupLength() const { return grouping + spacing; } inline uint16_t groupLength() const { return grouping + spacing; }
inline uint8_t getLightCapabilities() const { return _capabilities; } inline uint8_t getLightCapabilities() const { return _capabilities; }
inline void deactivate() { setGeometry(0,0); }
inline static uint16_t getUsedSegmentData() { return _usedSegmentData; } inline static unsigned getUsedSegmentData() { return Segment::_usedSegmentData; }
inline static void addUsedSegmentData(int len) { _usedSegmentData += len; } inline static void addUsedSegmentData(int len) { Segment::_usedSegmentData += len; }
#ifndef WLED_DISABLE_MODE_BLEND #ifndef WLED_DISABLE_MODE_BLEND
inline static void modeBlend(bool blend) { _modeBlend = blend; } inline static void modeBlend(bool blend) { _modeBlend = blend; }
#endif #endif
static void handleRandomPalette(); inline static unsigned vLength() { return Segment::_vLength; }
inline static unsigned vWidth() { return Segment::_vWidth; }
inline static unsigned vHeight() { return Segment::_vHeight; }
inline static uint32_t getCurrentColor(unsigned i) { return Segment::_currentColors[i]; } // { return i < 3 ? Segment::_currentColors[i] : 0; }
inline static const CRGBPalette16 &getCurrentPalette() { return Segment::_currentPalette; } inline static const CRGBPalette16 &getCurrentPalette() { return Segment::_currentPalette; }
inline static uint8_t getCurrentBrightness() { return Segment::_segBri; }
static void handleRandomPalette();
void setUp(uint16_t i1, uint16_t i2, uint8_t grp=1, uint8_t spc=0, uint16_t ofs=UINT16_MAX, uint16_t i1Y=0, uint16_t i2Y=1); void beginDraw(); // set up parameters for current effect
void setGeometry(uint16_t i1, uint16_t i2, uint8_t grp=1, uint8_t spc=0, uint16_t ofs=UINT16_MAX, uint16_t i1Y=0, uint16_t i2Y=1, uint8_t m12=0);
Segment &setColor(uint8_t slot, uint32_t c); Segment &setColor(uint8_t slot, uint32_t c);
Segment &setCCT(uint16_t k); Segment &setCCT(uint16_t k);
Segment &setOpacity(uint8_t o); Segment &setOpacity(uint8_t o);
Segment &setOption(uint8_t n, bool val); Segment &setOption(uint8_t n, bool val);
Segment &setMode(uint8_t fx, bool loadDefaults = false); Segment &setMode(uint8_t fx, bool loadDefaults = false);
Segment &setPalette(uint8_t pal); Segment &setPalette(uint8_t pal);
uint8_t differs(Segment& b) const; uint8_t differs(const Segment& b) const;
void refreshLightCapabilities(); void refreshLightCapabilities();
// runtime data functions // runtime data functions
@ -559,29 +612,33 @@ typedef struct Segment {
// transition functions // transition functions
void startTransition(uint16_t dur); // transition has to start before actual segment values change void startTransition(uint16_t dur); // transition has to start before actual segment values change
void stopTransition(); // ends transition mode by destroying transition structure (does nothing if not in transition) void stopTransition(); // ends transition mode by destroying transition structure (does nothing if not in transition)
inline void handleTransition() { if (progress() == 0xFFFFU) stopTransition(); } inline void handleTransition() { updateTransitionProgress(); if (progress() == 0xFFFFU) stopTransition(); }
#ifndef WLED_DISABLE_MODE_BLEND #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 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 #endif
[[gnu::hot]] uint16_t progress() const; // transition progression between 0-65535 [[gnu::hot]] void updateTransitionProgress(); // set current progression of transition
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) [[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) 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) [[gnu::hot]] uint32_t currentColor(uint8_t slot) const; // currently active segment color (blended while in transition)
CRGBPalette16 &loadPalette(CRGBPalette16 &tgt, uint8_t pal); CRGBPalette16 &loadPalette(CRGBPalette16 &tgt, uint8_t pal);
void setCurrentPalette();
// 1D strip // 1D strip
[[gnu::hot]] uint16_t virtualLength() const; [[gnu::hot]] uint16_t virtualLength() const;
[[gnu::hot]] void setPixelColor(int n, uint32_t c); // set relative pixel within segment with color [[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) { setPixelColor(int(n), c); } 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) { setPixelColor(n, RGBW32(r,g,b,w)); } 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) { setPixelColor(n, RGBW32(c.r,c.g,c.b,0)); } inline void setPixelColor(int n, CRGB c) const { setPixelColor(n, RGBW32(c.r,c.g,c.b,0)); }
#ifdef WLED_USE_AA_PIXELS #ifdef WLED_USE_AA_PIXELS
void setPixelColor(float i, uint32_t c, bool aa = true); 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) { setPixelColor(i, RGBW32(r,g,b,w), aa); } 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) { setPixelColor(i, RGBW32(c.r,c.g,c.b,0), aa); } inline void setPixelColor(float i, CRGB c, bool aa = true) const { setPixelColor(i, RGBW32(c.r,c.g,c.b,0), aa); }
#endif #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; [[gnu::hot]] uint32_t getPixelColor(int i) const;
// 1D support functions (some implement 2D as well) // 1D support functions (some implement 2D as well)
void blur(uint8_t, bool smear = false); void blur(uint8_t, bool smear = false);
@ -590,21 +647,19 @@ typedef struct Segment {
void fadeToBlackBy(uint8_t fadeBy); void fadeToBlackBy(uint8_t fadeBy);
inline void blendPixelColor(int n, uint32_t color, uint8_t blend) { setPixelColor(n, color_blend(getPixelColor(n), color, blend)); } inline void blendPixelColor(int n, uint32_t color, uint8_t blend) { setPixelColor(n, color_blend(getPixelColor(n), color, blend)); }
inline void blendPixelColor(int n, CRGB c, uint8_t blend) { blendPixelColor(n, RGBW32(c.r,c.g,c.b,0), blend); } inline void blendPixelColor(int n, CRGB c, uint8_t blend) { blendPixelColor(n, RGBW32(c.r,c.g,c.b,0), blend); }
inline void addPixelColor(int n, uint32_t color, bool fast = false) { setPixelColor(n, color_add(getPixelColor(n), color, fast)); } inline void addPixelColor(int n, uint32_t color, bool preserveCR = true) { setPixelColor(n, color_add(getPixelColor(n), color, preserveCR)); }
inline void addPixelColor(int n, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColor(n, RGBW32(r,g,b,w), fast); } inline void addPixelColor(int n, byte r, byte g, byte b, byte w = 0, bool preserveCR = true) { addPixelColor(n, RGBW32(r,g,b,w), preserveCR); }
inline void addPixelColor(int n, CRGB c, bool fast = false) { addPixelColor(n, RGBW32(c.r,c.g,c.b,0), fast); } inline void addPixelColor(int n, CRGB c, bool preserveCR = true) { addPixelColor(n, RGBW32(c.r,c.g,c.b,0), preserveCR); }
inline void fadePixelColor(uint16_t n, uint8_t fade) { setPixelColor(n, color_fade(getPixelColor(n), fade, true)); } inline void fadePixelColor(uint16_t n, uint8_t fade) { setPixelColor(n, color_fade(getPixelColor(n), fade, true)); }
[[gnu::hot]] uint32_t color_from_palette(uint16_t, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri = 255) const; [[gnu::hot]] uint32_t color_from_palette(uint16_t, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri = 255) const;
[[gnu::hot]] uint32_t color_wheel(uint8_t pos) const; [[gnu::hot]] uint32_t color_wheel(uint8_t pos) const;
// 2D Blur: shortcuts for bluring columns or rows only (50% faster than full 2D blur) // 2D Blur: shortcuts for bluring columns or rows only (50% faster than full 2D blur)
inline void blurCols(fract8 blur_amount, bool smear = false) { // blur all columns inline void blurCols(fract8 blur_amount, bool smear = false) { // blur all columns
const unsigned cols = virtualWidth(); blur2D(0, blur_amount, smear);
for (unsigned k = 0; k < cols; k++) blurCol(k, blur_amount, smear);
} }
inline void blurRows(fract8 blur_amount, bool smear = false) { // blur all rows inline void blurRows(fract8 blur_amount, bool smear = false) { // blur all rows
const unsigned rows = virtualHeight(); blur2D(blur_amount, 0, smear);
for ( unsigned i = 0; i < rows; i++) blurRow(i, blur_amount, smear);
} }
// 2D matrix // 2D matrix
@ -618,46 +673,46 @@ typedef struct Segment {
#endif #endif
} }
#ifndef WLED_DISABLE_2D #ifndef WLED_DISABLE_2D
[[gnu::hot]] uint16_t XY(int x, int y); // support function to get relative index within segment inline bool is2D() const { return (width()>1 && height()>1); }
[[gnu::hot]] void setPixelColorXY(int x, int y, uint32_t c); // set relative pixel within segment with color [[gnu::hot]] int XY(int x, int y) const; // support function to get relative index within segment
inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColorXY(int(x), int(y), c); } [[gnu::hot]] void setPixelColorXY(int x, int y, uint32_t c) const; // set relative pixel within segment with color
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(unsigned x, unsigned y, uint32_t c) const { setPixelColorXY(int(x), int(y), c); }
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, byte r, byte g, byte b, byte w = 0) const { setPixelColorXY(x, y, RGBW32(r,g,b,w)); }
inline void setPixelColorXY(unsigned x, unsigned y, CRGB c) { setPixelColorXY(int(x), int(y), RGBW32(c.r,c.g,c.b,0)); } 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 #ifdef WLED_USE_AA_PIXELS
void setPixelColorXY(float x, float y, uint32_t c, bool aa = true); 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) { setPixelColorXY(x, y, RGBW32(r,g,b,w), aa); } 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) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), 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 #endif
[[gnu::hot]] bool isPixelXYClipped(int x, int y) const;
[[gnu::hot]] uint32_t getPixelColorXY(int x, int y) const; [[gnu::hot]] uint32_t getPixelColorXY(int x, int y) const;
// 2D support functions // 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)); } 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)); }
inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), blend); } inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), blend); }
inline void addPixelColorXY(int x, int y, uint32_t color, bool fast = false) { setPixelColorXY(x, y, color_add(getPixelColorXY(x,y), color, fast)); } inline void addPixelColorXY(int x, int y, uint32_t color, bool preserveCR = true) { setPixelColorXY(x, y, color_add(getPixelColorXY(x,y), color, preserveCR)); }
inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColorXY(x, y, RGBW32(r,g,b,w), fast); } inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool preserveCR = true) { addPixelColorXY(x, y, RGBW32(r,g,b,w), preserveCR); }
inline void addPixelColorXY(int x, int y, CRGB c, bool fast = false) { addPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), fast); } inline void addPixelColorXY(int x, int y, CRGB c, bool preserveCR = true) { addPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), preserveCR); }
inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), fade, true)); } inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), fade, true)); }
void box_blur(unsigned r = 1U, bool smear = false); // 2D box blur //void box_blur(unsigned r = 1U, bool smear = false); // 2D box blur
void blur2D(uint8_t blur_amount, bool smear = false); void blur2D(uint8_t blur_x, uint8_t blur_y, bool smear = false);
void blurRow(uint32_t row, fract8 blur_amount, bool smear = false); void moveX(int delta, bool wrap = false);
void blurCol(uint32_t col, fract8 blur_amount, bool smear = false); void moveY(int delta, bool wrap = false);
void moveX(int8_t delta, bool wrap = false); void move(unsigned dir, unsigned delta, bool wrap = false);
void moveY(int8_t delta, bool wrap = false);
void move(uint8_t dir, uint8_t delta, bool wrap = false);
void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false); void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false);
inline void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) { drawCircle(cx, cy, radius, RGBW32(c.r,c.g,c.b,0), soft); } inline void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) { drawCircle(cx, cy, radius, RGBW32(c.r,c.g,c.b,0), soft); }
void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false); void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false);
inline void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) { fillCircle(cx, cy, radius, RGBW32(c.r,c.g,c.b,0), soft); } inline void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) { fillCircle(cx, cy, radius, RGBW32(c.r,c.g,c.b,0), soft); }
void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft = false); void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft = false);
inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c, bool soft = false) { drawLine(x0, y0, x1, y1, RGBW32(c.r,c.g,c.b,0), soft); } // automatic inline inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c, bool soft = false) { drawLine(x0, y0, x1, y1, RGBW32(c.r,c.g,c.b,0), soft); } // automatic inline
void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2 = 0, int8_t rotate = 0); void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2 = 0, int8_t rotate = 0, bool usePalGrad = false);
inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c) { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0)); } // automatic inline inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c) { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0)); } // automatic inline
inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2, int8_t rotate = 0) { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0), RGBW32(c2.r,c2.g,c2.b,0), rotate); } // automatic inline inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2, int8_t rotate = 0, bool usePalGrad = false) { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0), RGBW32(c2.r,c2.g,c2.b,0), rotate, usePalGrad); } // automatic inline
void wu_pixel(uint32_t x, uint32_t y, CRGB c); void wu_pixel(uint32_t x, uint32_t y, CRGB c);
inline void blur2d(fract8 blur_amount) { blur(blur_amount); }
inline void fill_solid(CRGB c) { fill(RGBW32(c.r,c.g,c.b,0)); } inline void fill_solid(CRGB c) { fill(RGBW32(c.r,c.g,c.b,0)); }
#else #else
inline uint16_t XY(uint16_t x, uint16_t 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(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(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)); } inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColor(x, RGBW32(r,g,b,w)); }
@ -668,19 +723,20 @@ 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, 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); } inline void setPixelColorXY(float x, float y, CRGB c, bool aa = true) { setPixelColor(x, RGBW32(c.r,c.g,c.b,0), aa); }
#endif #endif
inline bool isPixelXYClipped(int x, int y) { return isPixelClipped(x); }
inline uint32_t getPixelColorXY(int x, int y) { return getPixelColor(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, 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); } 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); }
inline void addPixelColorXY(int x, int y, uint32_t color, bool fast = false) { addPixelColor(x, color, fast); } inline void addPixelColorXY(int x, int y, uint32_t color, bool saturate = false) { addPixelColor(x, color, saturate); }
inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColor(x, RGBW32(r,g,b,w), fast); } inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool saturate = false) { addPixelColor(x, RGBW32(r,g,b,w), saturate); }
inline void addPixelColorXY(int x, int y, CRGB c, bool fast = false) { addPixelColor(x, RGBW32(c.r,c.g,c.b,0), fast); } inline void addPixelColorXY(int x, int y, CRGB c, bool saturate = false) { addPixelColor(x, RGBW32(c.r,c.g,c.b,0), saturate); }
inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { fadePixelColor(x, fade); } inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { fadePixelColor(x, fade); }
inline void box_blur(unsigned i, bool vertical, fract8 blur_amount) {} //inline void box_blur(unsigned i, bool vertical, fract8 blur_amount) {}
inline void blur2D(uint8_t blur_amount, bool smear = false) {} inline void blur2D(uint8_t blur_x, uint8_t blur_y, bool smear = false) {}
inline void blurRow(uint32_t row, fract8 blur_amount, bool smear = false) {} inline void blurRow(int row, fract8 blur_amount, bool smear = false) {}
inline void blurCol(uint32_t col, fract8 blur_amount, bool smear = false) {} inline void blurCol(int col, fract8 blur_amount, bool smear = false) {}
inline void moveX(int8_t delta, bool wrap = false) {} inline void moveX(int delta, bool wrap = false) {}
inline void moveY(int8_t delta, bool wrap = false) {} inline void moveY(int delta, bool wrap = false) {}
inline void move(uint8_t dir, uint8_t delta, bool wrap = false) {} inline void move(uint8_t dir, uint8_t delta, bool wrap = false) {}
inline void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false) {} inline void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false) {}
inline void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) {} inline void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) {}
@ -688,9 +744,9 @@ typedef struct Segment {
inline void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) {} inline void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) {}
inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft = false) {} inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft = false) {}
inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c, bool soft = false) {} inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c, bool soft = false) {}
inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t = 0, int8_t = 0) {} inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t = 0, int8_t = 0, bool = false) {}
inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB color) {} inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB color) {}
inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2, int8_t rotate = 0) {} inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2, int8_t rotate = 0, bool usePalGrad = false) {}
inline void wu_pixel(uint32_t x, uint32_t y, CRGB c) {} inline void wu_pixel(uint32_t x, uint32_t y, CRGB c) {}
#endif #endif
} segment; } segment;
@ -712,9 +768,7 @@ class WS2812FX { // 96 bytes
public: public:
WS2812FX() : WS2812FX() :
paletteFade(0),
paletteBlend(0), paletteBlend(0),
cctBlending(0),
now(millis()), now(millis()),
timebase(0), timebase(0),
isMatrix(false), isMatrix(false),
@ -728,9 +782,6 @@ class WS2812FX { // 96 bytes
#endif #endif
correctWB(false), correctWB(false),
cctFromRgb(false), cctFromRgb(false),
// semi-private (just obscured) used in effect functions through macros
_colors_t{0,0,0},
_virtualSegmentLength(0),
// true private variables // true private variables
_suspend(false), _suspend(false),
_length(DEFAULT_LED_COUNT), _length(DEFAULT_LED_COUNT),
@ -748,6 +799,7 @@ class WS2812FX { // 96 bytes
customMappingTable(nullptr), customMappingTable(nullptr),
customMappingSize(0), customMappingSize(0),
_lastShow(0), _lastShow(0),
_lastServiceShow(0),
_segment_index(0), _segment_index(0),
_mainSegment(0) _mainSegment(0)
{ {
@ -759,7 +811,7 @@ class WS2812FX { // 96 bytes
} }
~WS2812FX() { ~WS2812FX() {
if (customMappingTable) delete[] customMappingTable; if (customMappingTable) free(customMappingTable);
_mode.clear(); _mode.clear();
_modeData.clear(); _modeData.clear();
_segments.clear(); _segments.clear();
@ -777,29 +829,25 @@ class WS2812FX { // 96 bytes
#endif #endif
finalizeInit(), // initialises strip components finalizeInit(), // initialises strip components
service(), // executes effect functions when due and calls strip.show() service(), // executes effect functions when due and calls strip.show()
setMode(uint8_t segid, uint8_t m), // sets effect/mode for given segment (high level API)
setColor(uint8_t slot, uint32_t c), // sets color (in slot) for given segment (high level API)
setCCT(uint16_t k), // sets global CCT (either in relative 0-255 value or in K) setCCT(uint16_t k), // sets global CCT (either in relative 0-255 value or in K)
setBrightness(uint8_t b, bool direct = false), // sets strip brightness setBrightness(uint8_t b, bool direct = false), // sets strip brightness
setRange(uint16_t i, uint16_t i2, uint32_t col), // used for clock overlay setRange(uint16_t i, uint16_t i2, uint32_t col), // used for clock overlay
purgeSegments(), // removes inactive segments from RAM (may incure penalty and memory fragmentation but reduces vector footprint) purgeSegments(), // removes inactive segments from RAM (may incure penalty and memory fragmentation but reduces vector footprint)
setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t grouping = 1, uint8_t spacing = 0, uint16_t offset = UINT16_MAX, uint16_t startY=0, uint16_t stopY=1), setMainSegmentId(unsigned n = 0),
setMainSegmentId(uint8_t n),
resetSegments(), // marks all segments for reset resetSegments(), // marks all segments for reset
makeAutoSegments(bool forceReset = false), // will create segments based on configured outputs makeAutoSegments(bool forceReset = false), // will create segments based on configured outputs
fixInvalidSegments(), // fixes incorrect segment configuration 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 show(), // initiates LED output
setTargetFps(uint8_t fps), setTargetFps(unsigned fps),
setupEffectData(); // add default effects to the list; defined in FX.cpp setupEffectData(); // add default effects to the list; defined in FX.cpp
inline void resetTimebase() { timebase = 0UL - millis(); } inline void resetTimebase() { timebase = 0UL - millis(); }
inline void restartRuntime() { for (Segment &seg : _segments) { seg.markForReset().resetIfRequired(); } } 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 setTransitionMode(bool t) { for (Segment &seg : _segments) seg.startTransition(t ? _transitionDur : 0); }
inline void setColor(uint8_t slot, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { setColor(slot, RGBW32(r,g,b,w)); } 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, 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) const { setPixelColor(n, c.red, c.green, c.blue); }
inline void setPixelColor(unsigned n, CRGB c) { 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 fill(uint32_t c) { 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 trigger() { _triggered = true; } // Forces the next frame to be computed on all active segments.
inline void setShowCallback(show_callback cb) { _callback = cb; } inline void setShowCallback(show_callback cb) { _callback = cb; }
inline void setTransition(uint16_t t) { _transitionDur = t; } // sets transition time (in ms) inline void setTransition(uint16_t t) { _transitionDur = t; } // sets transition time (in ms)
@ -808,13 +856,12 @@ class WS2812FX { // 96 bytes
inline void resume() { _suspend = false; } // will resume strip.service() execution inline void resume() { _suspend = false; } // will resume strip.service() execution
bool bool
paletteFade, checkSegmentAlignment() const,
checkSegmentAlignment(),
hasRGBWBus() const, hasRGBWBus() const,
hasCCTBus() const, hasCCTBus() const,
isUpdating() const, // return true if the strip is being sent pixel updates deserializeMap(unsigned n = 0);
deserializeMap(uint8_t n=0);
inline bool isUpdating() const { return !BusManager::canAllShow(); } // return true if the strip is being sent pixel updates
inline bool isServicing() const { return _isServicing; } // returns true if strip.service() is executing inline bool isServicing() const { return _isServicing; } // returns true if strip.service() is executing
inline bool hasWhiteChannel() const { return _hasWhiteChannel; } // returns true if strip contains separate white chanel inline bool hasWhiteChannel() const { return _hasWhiteChannel; } // returns true if strip contains separate white chanel
inline bool isOffRefreshRequired() const { return _isOffRefreshRequired; } // returns true if strip requires regular updates (i.e. TM1814 chipset) inline bool isOffRefreshRequired() const { return _isOffRefreshRequired; } // returns true if strip requires regular updates (i.e. TM1814 chipset)
@ -823,7 +870,6 @@ class WS2812FX { // 96 bytes
uint8_t uint8_t
paletteBlend, paletteBlend,
cctBlending,
getActiveSegmentsNum() const, getActiveSegmentsNum() const,
getFirstSelectedSegId() const, getFirstSelectedSegId() const,
getLastActiveSegmentId() const, getLastActiveSegmentId() const,
@ -831,7 +877,7 @@ class WS2812FX { // 96 bytes
addEffect(uint8_t id, mode_ptr mode_fn, const char *mode_name); // add effect to the list; defined in FX.cpp; addEffect(uint8_t id, mode_ptr mode_fn, const char *mode_name); // add effect to the list; defined in FX.cpp;
inline uint8_t getBrightness() const { return _brightness; } // returns current strip brightness inline uint8_t getBrightness() const { return _brightness; } // returns current strip brightness
inline uint8_t getMaxSegments() const { return MAX_NUM_SEGMENTS; } // returns maximum number of supported segments (fixed value) inline static constexpr unsigned getMaxSegments() { return MAX_NUM_SEGMENTS; } // returns maximum number of supported segments (fixed value)
inline uint8_t getSegmentsNum() const { return _segments.size(); } // returns currently present segments inline uint8_t getSegmentsNum() const { return _segments.size(); } // returns currently present segments
inline uint8_t getCurrSegmentId() const { return _segment_index; } // returns current segment index (only valid while strip.isServicing()) inline uint8_t getCurrSegmentId() const { return _segment_index; } // returns current segment index (only valid while strip.isServicing())
inline uint8_t getMainSegmentId() const { return _mainSegment; } // returns main segment index inline uint8_t getMainSegmentId() const { return _mainSegment; } // returns main segment index
@ -841,28 +887,27 @@ class WS2812FX { // 96 bytes
uint16_t uint16_t
getLengthPhysical() const, getLengthPhysical() const,
getLengthTotal() const, // will include virtual/nonexistent pixels in matrix getLengthTotal() const; // will include virtual/nonexistent pixels in matrix
getFps() const,
getMappedPixelIndex(uint16_t index) const;
inline uint16_t getFps() const { return (millis() - _lastShow > 2000) ? 0 : (FPS_MULTIPLIER * _cumulativeFps) >> FPS_CALC_SHIFT; } // Returns the refresh rate of the LED strip (_cumulativeFps is stored in fixed point)
inline uint16_t getFrameTime() const { return _frametime; } // returns amount of time a frame should take (in ms) inline uint16_t getFrameTime() const { return _frametime; } // returns amount of time a frame should take (in ms)
inline uint16_t getMinShowDelay() const { return MIN_SHOW_DELAY; } // returns minimum amount of time strip.service() can be delayed (constant) inline uint16_t getMinShowDelay() const { return MIN_FRAME_DELAY; } // returns minimum amount of time strip.service() can be delayed (constant)
inline uint16_t getLength() const { return _length; } // returns actual amount of LEDs on a strip (2D matrix may have less LEDs than W*H) inline uint16_t getLength() const { return _length; } // returns actual amount of LEDs on a strip (2D matrix may have less LEDs than W*H)
inline uint16_t getTransition() const { return _transitionDur; } // returns currently set transition time (in ms) inline uint16_t getTransition() const { return _transitionDur; } // returns currently set transition time (in ms)
inline uint16_t getMappedPixelIndex(uint16_t index) const { // convert logical address to physical
if (index < customMappingSize && (realtimeMode == REALTIME_MODE_INACTIVE || realtimeRespectLedMaps)) index = customMappingTable[index];
return index;
};
unsigned long now, timebase; 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 inline uint32_t getLastShow() const { return _lastShow; } // returns millis() timestamp of last strip.show() call
inline uint32_t segColor(uint8_t i) const { return _colors_t[i]; } // returns currently valid color (for slot i) AKA SEGCOLOR(); may be blended between two colors while in transition
const char * const char *getModeData(unsigned id = 0) const { return (id && id < _modeCount) ? _modeData[id] : PSTR("Solid"); }
getModeData(uint8_t id = 0) const { return (id && id<_modeCount) ? _modeData[id] : PSTR("Solid"); } inline const char **getModeDataSrc() { return &(_modeData[0]); } // vectors use arrays for underlying data
const char ** Segment& getSegment(unsigned id);
getModeDataSrc() { return &(_modeData[0]); } // vectors use arrays for underlying data
Segment& getSegment(uint8_t id);
inline Segment& getFirstSelectedSeg() { return _segments[getFirstSelectedSegId()]; } // returns reference to first segment that is "selected" inline Segment& getFirstSelectedSeg() { return _segments[getFirstSelectedSegId()]; } // returns reference to first segment that is "selected"
inline Segment& getMainSegment() { return _segments[getMainSegmentId()]; } // returns reference to main segment inline Segment& getMainSegment() { return _segments[getMainSegmentId()]; } // returns reference to main segment
inline Segment* getSegments() { return &(_segments[0]); } // returns pointer to segment vector structure (warning: use carefully) inline Segment* getSegments() { return &(_segments[0]); } // returns pointer to segment vector structure (warning: use carefully)
@ -904,9 +949,9 @@ class WS2812FX { // 96 bytes
void setUpMatrix(); // sets up automatic matrix ledmap from panel configuration void setUpMatrix(); // sets up automatic matrix ledmap from panel configuration
// outsmart the compiler :) by correctly overloading // 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, 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) { setPixelColorXY(x, y, RGBW32(r,g,b,w)); } 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) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } 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); }
@ -921,13 +966,8 @@ class WS2812FX { // 96 bytes
bool cctFromRgb : 1; bool cctFromRgb : 1;
}; };
// using public variables to reduce code size increase due to inline function getSegment() (with bounds checking)
// and color transitions
uint32_t _colors_t[3]; // color used for effect (includes transition)
uint16_t _virtualSegmentLength;
std::vector<segment> _segments; std::vector<segment> _segments;
friend class Segment; friend struct Segment;
private: private:
volatile bool _suspend; volatile bool _suspend;
@ -958,6 +998,7 @@ class WS2812FX { // 96 bytes
uint16_t customMappingSize; uint16_t customMappingSize;
unsigned long _lastShow; unsigned long _lastShow;
unsigned long _lastServiceShow;
uint8_t _segment_index; uint8_t _segment_index;
uint8_t _mainSegment; uint8_t _mainSegment;

View File

@ -50,8 +50,8 @@ void WS2812FX::setUpMatrix() {
customMappingSize = 0; // prevent use of mapping if anything goes wrong customMappingSize = 0; // prevent use of mapping if anything goes wrong
if (customMappingTable) delete[] customMappingTable; if (customMappingTable) free(customMappingTable);
customMappingTable = new uint16_t[getLengthTotal()]; customMappingTable = static_cast<uint16_t*>(malloc(sizeof(uint16_t)*getLengthTotal()));
if (customMappingTable) { if (customMappingTable) {
customMappingSize = getLengthTotal(); 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,...] // 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 // 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) // 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); bool isFile = WLED_FS.exists(fileName);
size_t gapSize = 0; size_t gapSize = 0;
int8_t *gapTable = nullptr; int8_t *gapTable = nullptr;
@ -85,7 +85,7 @@ void WS2812FX::setUpMatrix() {
JsonArray map = pDoc->as<JsonArray>(); JsonArray map = pDoc->as<JsonArray>();
gapSize = map.size(); gapSize = map.size();
if (!map.isNull() && gapSize >= matrixSize) { // not an empty map 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++) { if (gapTable) for (size_t i = 0; i < gapSize; i++) {
gapTable[i] = constrain(map[i], -1, 1); gapTable[i] = constrain(map[i], -1, 1);
} }
@ -113,7 +113,7 @@ void WS2812FX::setUpMatrix() {
} }
// delete gap array as we no longer need it // delete gap array as we no longer need it
if (gapTable) delete[] gapTable; if (gapTable) free(gapTable);
#ifdef WLED_DEBUG #ifdef WLED_DEBUG
DEBUG_PRINT(F("Matrix ledmap:")); DEBUG_PRINT(F("Matrix ledmap:"));
@ -146,74 +146,125 @@ void WS2812FX::setUpMatrix() {
#ifndef WLED_DISABLE_2D #ifndef WLED_DISABLE_2D
// XY(x,y) - gets pixel index within current segment (often used to reference leds[] array element) // 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
{ {
unsigned width = virtualWidth(); // segment width in logical pixels (can be 0 if segment is inactive) const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
unsigned height = virtualHeight(); // segment height in logical pixels (is always >= 1) const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
return isActive() ? (x%width) + (y%height) * width : 0; return isActive() ? (x%vW) + (y%vH) * vW : 0;
} }
void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) // raw setColor function without checks (checks are done in setPixelColorXY())
void IRAM_ATTR_YN Segment::_setPixelColorXY_raw(const int& x, const int& y, uint32_t& col) const
{ {
if (!isActive()) return; // not active const int baseX = start + x;
if ((unsigned)x >= virtualWidth() || (unsigned)y >= virtualHeight() || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit const int baseY = startY + y;
uint8_t _bri_t = currentBri();
if (_bri_t < 255) {
col = color_fade(col, _bri_t);
}
if (reverse ) x = virtualWidth() - x - 1;
if (reverse_y) y = virtualHeight() - y - 1;
if (transpose) { std::swap(x,y); } // swap X & Y if segment transposed
x *= groupLength(); // expand to physical pixels
y *= groupLength(); // expand to physical pixels
int W = width();
int H = height();
if (x >= W || y >= H) return; // if pixel would fall out of segment just exit
uint32_t tmpCol = col;
for (int j = 0; j < grouping; j++) { // groupping vertically
for (int g = 0; g < grouping; g++) { // groupping horizontally
int xX = (x+g), yY = (y+j);
if (xX >= W || yY >= H) continue; // we have reached one dimension's end
#ifndef WLED_DISABLE_MODE_BLEND #ifndef WLED_DISABLE_MODE_BLEND
// if blending modes, blend with underlying pixel // if blending modes, blend with underlying pixel
if (_modeBlend) tmpCol = color_blend(strip.getPixelColorXY(start + xX, startY + yY), col, 0xFFFFU - progress(), true); if (_modeBlend && blendingStyle == BLEND_STYLE_FADE) col = color_blend16(strip.getPixelColorXY(baseX, baseY), col, 0xFFFFU - progress());
#endif
strip.setPixelColorXY(baseX, baseY, col);
// Apply mirroring
if (mirror || mirror_y) {
auto setMirroredPixel = [&](int mx, int my) {
strip.setPixelColorXY(mx, my, col);
};
const int mirrorX = start + width() - x - 1;
const int mirrorY = startY + height() - y - 1;
if (mirror) setMirroredPixel(transpose ? baseX : mirrorX, transpose ? mirrorY : baseY);
if (mirror_y) setMirroredPixel(transpose ? mirrorX : baseX, transpose ? baseY : mirrorY);
if (mirror && mirror_y) setMirroredPixel(mirrorX, mirrorY);
}
}
// 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)
#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 #endif
strip.setPixelColorXY(start + xX, startY + yY, tmpCol); if (x >= vW || y >= vH || x < 0 || y < 0 || isPixelXYClipped(x,y)) return; // if pixel would fall out of virtual segment just exit
if (mirror) { //set the corresponding horizontally mirrored pixel // if color is unscaled
if (transpose) strip.setPixelColorXY(start + xX, startY + height() - yY - 1, tmpCol); if (!_colorScaled) col = color_fade(col, _segBri);
else strip.setPixelColorXY(start + width() - xX - 1, startY + yY, tmpCol);
} if (reverse ) x = vW - x - 1;
if (mirror_y) { //set the corresponding vertically mirrored pixel if (reverse_y) y = vH - y - 1;
if (transpose) strip.setPixelColorXY(start + width() - xX - 1, startY + yY, tmpCol); if (transpose) { std::swap(x,y); } // swap X & Y if segment transposed
else strip.setPixelColorXY(start + xX, startY + height() - yY - 1, tmpCol); unsigned groupLen = groupLength();
}
if (mirror_y && mirror) { //set the corresponding vertically AND horizontally mirrored pixel if (groupLen > 1) {
strip.setPixelColorXY(start + width() - xX - 1, startY + height() - yY - 1, tmpCol); int W = width();
int H = height();
x *= groupLen; // expand to physical pixels
y *= groupLen; // expand to physical pixels
const int maxY = std::min(y + grouping, H);
const int maxX = std::min(x + grouping, W);
for (int yY = y; yY < maxY; yY++) {
for (int xX = x; xX < maxX; xX++) {
_setPixelColorXY_raw(xX, yY, col);
} }
} }
} else {
_setPixelColorXY_raw(x, y, col);
} }
} }
#ifdef WLED_USE_AA_PIXELS #ifdef WLED_USE_AA_PIXELS
// anti-aliased version of setPixelColorXY() // 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 (!isActive()) return; // not active
if (x<0.0f || x>1.0f || y<0.0f || y>1.0f) return; // not normalized if (x<0.0f || x>1.0f || y<0.0f || y>1.0f) return; // not normalized
const unsigned cols = virtualWidth(); float fX = x * (vWidth()-1);
const unsigned rows = virtualHeight(); float fY = y * (vHeight()-1);
float fX = x * (cols-1);
float fY = y * (rows-1);
if (aa) { if (aa) {
unsigned xL = roundf(fX-0.49f); unsigned xL = roundf(fX-0.49f);
unsigned xR = roundf(fX+0.49f); unsigned xR = roundf(fX+0.49f);
@ -251,9 +302,26 @@ void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa)
// returns RGBW values of pixel // returns RGBW values of pixel
uint32_t IRAM_ATTR_YN Segment::getPixelColorXY(int x, int y) const { uint32_t IRAM_ATTR_YN Segment::getPixelColorXY(int x, int y) const {
if (!isActive()) return 0; // not active if (!isActive()) return 0; // not active
if ((unsigned)x >= virtualWidth() || (unsigned)y >= virtualHeight() || x<0 || y<0) return 0; // if pixel would fall out of virtual segment just exit
if (reverse ) x = virtualWidth() - x - 1; const int vW = vWidth();
if (reverse_y) y = virtualHeight() - y - 1; const int vH = vHeight();
#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 if (transpose) { std::swap(x,y); } // swap X & Y if segment transposed
x *= groupLength(); // expand to physical pixels x *= groupLength(); // expand to physical pixels
y *= groupLength(); // expand to physical pixels y *= groupLength(); // expand to physical pixels
@ -261,91 +329,26 @@ uint32_t IRAM_ATTR_YN Segment::getPixelColorXY(int x, int y) const {
return strip.getPixelColorXY(start + x, startY + y); return strip.getPixelColorXY(start + x, startY + y);
} }
// blurRow: perform a blur on a row of a rectangular matrix // 2D blurring, can be asymmetrical
void Segment::blurRow(uint32_t row, fract8 blur_amount, bool smear){ void Segment::blur2D(uint8_t blur_x, uint8_t blur_y, bool smear) {
if (!isActive() || blur_amount == 0) return; // not active if (!isActive()) return; // not active
const unsigned cols = virtualWidth(); const unsigned cols = vWidth();
const unsigned rows = virtualHeight(); const unsigned rows = vHeight();
uint32_t lastnew; // not necessary to initialize lastnew and last, as both will be initialized by the first loop iteration
if (row >= rows) return;
// blur one row
uint8_t keep = smear ? 255 : 255 - blur_amount;
uint8_t seep = blur_amount >> 1;
uint32_t carryover = BLACK;
uint32_t lastnew;
uint32_t last; uint32_t last;
uint32_t curnew = BLACK; if (blur_x) {
for (unsigned x = 0; x < cols; x++) { const uint8_t keepx = smear ? 255 : 255 - blur_x;
uint32_t cur = getPixelColorXY(x, row); const uint8_t seepx = blur_x >> 1;
uint32_t part = color_fade(cur, seep); for (unsigned row = 0; row < rows; row++) { // blur rows (x direction)
curnew = color_fade(cur, keep);
if (x > 0) {
if (carryover)
curnew = color_add(curnew, carryover, true);
uint32_t prev = color_add(lastnew, part, true);
if (last != prev) // optimization: only set pixel if color has changed
setPixelColorXY(x - 1, row, prev);
} else // first pixel
setPixelColorXY(x, row, curnew);
lastnew = curnew;
last = cur; // save original value for comparison on next iteration
carryover = part;
}
setPixelColorXY(cols-1, row, curnew); // set last pixel
}
// blurCol: perform a blur on a column of a rectangular matrix
void Segment::blurCol(uint32_t col, fract8 blur_amount, bool smear) {
if (!isActive() || blur_amount == 0) return; // not active
const unsigned cols = virtualWidth();
const unsigned rows = virtualHeight();
if (col >= cols) return;
// blur one column
uint8_t keep = smear ? 255 : 255 - blur_amount;
uint8_t seep = blur_amount >> 1;
uint32_t carryover = BLACK;
uint32_t lastnew;
uint32_t last;
uint32_t curnew = BLACK;
for (unsigned y = 0; y < rows; y++) {
uint32_t cur = getPixelColorXY(col, y);
uint32_t part = color_fade(cur, seep);
curnew = color_fade(cur, keep);
if (y > 0) {
if (carryover)
curnew = color_add(curnew, carryover, true);
uint32_t prev = color_add(lastnew, part, true);
if (last != prev) // optimization: only set pixel if color has changed
setPixelColorXY(col, y - 1, prev);
} else // first pixel
setPixelColorXY(col, y, curnew);
lastnew = curnew;
last = cur; //save original value for comparison on next iteration
carryover = part;
}
setPixelColorXY(col, rows - 1, curnew);
}
void Segment::blur2D(uint8_t blur_amount, bool smear) {
if (!isActive() || blur_amount == 0) return; // not active
const unsigned cols = virtualWidth();
const unsigned rows = virtualHeight();
const uint8_t keep = smear ? 255 : 255 - blur_amount;
const uint8_t seep = blur_amount >> (1 + smear);
uint32_t lastnew;
uint32_t last;
for (unsigned row = 0; row < rows; row++) {
uint32_t carryover = BLACK; uint32_t carryover = BLACK;
uint32_t curnew = BLACK; uint32_t curnew = BLACK;
for (unsigned x = 0; x < cols; x++) { for (unsigned x = 0; x < cols; x++) {
uint32_t cur = getPixelColorXY(x, row); uint32_t cur = getPixelColorXY(x, row);
uint32_t part = color_fade(cur, seep); uint32_t part = color_fade(cur, seepx);
curnew = color_fade(cur, keep); curnew = color_fade(cur, keepx);
if (x > 0) { if (x > 0) {
if (carryover) curnew = color_add(curnew, carryover, true); if (carryover) curnew = color_add(curnew, carryover);
uint32_t prev = color_add(lastnew, part, true); uint32_t prev = color_add(lastnew, part);
// optimization: only set pixel if color has changed // optimization: only set pixel if color has changed
if (last != prev) setPixelColorXY(x - 1, row, prev); if (last != prev) setPixelColorXY(x - 1, row, prev);
} else setPixelColorXY(x, row, curnew); // first pixel } else setPixelColorXY(x, row, curnew); // first pixel
@ -355,16 +358,20 @@ void Segment::blur2D(uint8_t blur_amount, bool smear) {
} }
setPixelColorXY(cols-1, row, curnew); // set last pixel setPixelColorXY(cols-1, row, curnew); // set last pixel
} }
}
if (blur_y) {
const uint8_t keepy = smear ? 255 : 255 - blur_y;
const uint8_t seepy = blur_y >> 1;
for (unsigned col = 0; col < cols; col++) { for (unsigned col = 0; col < cols; col++) {
uint32_t carryover = BLACK; uint32_t carryover = BLACK;
uint32_t curnew = BLACK; uint32_t curnew = BLACK;
for (unsigned y = 0; y < rows; y++) { for (unsigned y = 0; y < rows; y++) {
uint32_t cur = getPixelColorXY(col, y); uint32_t cur = getPixelColorXY(col, y);
uint32_t part = color_fade(cur, seep); uint32_t part = color_fade(cur, seepy);
curnew = color_fade(cur, keep); curnew = color_fade(cur, keepy);
if (y > 0) { if (y > 0) {
if (carryover) curnew = color_add(curnew, carryover, true); if (carryover) curnew = color_add(curnew, carryover);
uint32_t prev = color_add(lastnew, part, true); uint32_t prev = color_add(lastnew, part);
// optimization: only set pixel if color has changed // optimization: only set pixel if color has changed
if (last != prev) setPixelColorXY(col, y - 1, prev); if (last != prev) setPixelColorXY(col, y - 1, prev);
} else setPixelColorXY(col, y, curnew); // first pixel } else setPixelColorXY(col, y, curnew); // first pixel
@ -374,15 +381,17 @@ void Segment::blur2D(uint8_t blur_amount, bool smear) {
} }
setPixelColorXY(col, rows - 1, curnew); setPixelColorXY(col, rows - 1, curnew);
} }
}
} }
/*
// 2D Box blur // 2D Box blur
void Segment::box_blur(unsigned radius, bool smear) { void Segment::box_blur(unsigned radius, bool smear) {
if (!isActive() || radius == 0) return; // not active if (!isActive() || radius == 0) return; // not active
if (radius > 3) radius = 3; if (radius > 3) radius = 3;
const unsigned d = (1 + 2*radius) * (1 + 2*radius); // averaging divisor const unsigned d = (1 + 2*radius) * (1 + 2*radius); // averaging divisor
const unsigned cols = virtualWidth(); const unsigned cols = vWidth();
const unsigned rows = virtualHeight(); const unsigned rows = vHeight();
uint16_t *tmpRSum = new uint16_t[cols*rows]; uint16_t *tmpRSum = new uint16_t[cols*rows];
uint16_t *tmpGSum = new uint16_t[cols*rows]; uint16_t *tmpGSum = new uint16_t[cols*rows];
uint16_t *tmpBSum = new uint16_t[cols*rows]; uint16_t *tmpBSum = new uint16_t[cols*rows];
@ -448,40 +457,56 @@ void Segment::box_blur(unsigned radius, bool smear) {
delete[] tmpBSum; delete[] tmpBSum;
delete[] tmpWSum; delete[] tmpWSum;
} }
*/
void Segment::moveX(int8_t delta, bool wrap) { void Segment::moveX(int delta, bool wrap) {
if (!isActive()) return; // not active if (!isActive() || !delta) return; // not active
const int cols = virtualWidth(); const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
const int rows = virtualHeight(); const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
if (!delta || abs(delta) >= cols) return; int absDelta = abs(delta);
uint32_t newPxCol[cols]; if (absDelta >= vW) return;
for (int y = 0; y < rows; y++) { uint32_t newPxCol[vW];
if (delta > 0) { int newDelta;
for (int x = 0; x < cols-delta; x++) newPxCol[x] = getPixelColorXY((x + delta), y); int stop = vW;
for (int x = cols-delta; x < cols; x++) newPxCol[x] = getPixelColorXY(wrap ? (x + delta) - cols : x, y); int start = 0;
} else { if (wrap) newDelta = (delta + vW) % vW; // +cols in case delta < 0
for (int x = cols-1; x >= -delta; x--) newPxCol[x] = getPixelColorXY((x + delta), y); else {
for (int x = -delta-1; x >= 0; x--) newPxCol[x] = getPixelColorXY(wrap ? (x + delta) + cols : x, y); if (delta < 0) start = absDelta;
stop = vW - absDelta;
newDelta = delta > 0 ? delta : 0;
} }
for (int x = 0; x < cols; x++) setPixelColorXY(x, y, newPxCol[x]); for (int y = 0; y < vH; y++) {
for (int x = 0; x < stop; x++) {
int srcX = x + newDelta;
if (wrap) srcX %= vW; // Wrap using modulo when `wrap` is true
newPxCol[x] = getPixelColorXY(srcX, y);
}
for (int x = 0; x < stop; x++) setPixelColorXY(x + start, y, newPxCol[x]);
} }
} }
void Segment::moveY(int8_t delta, bool wrap) { void Segment::moveY(int delta, bool wrap) {
if (!isActive()) return; // not active if (!isActive() || !delta) return; // not active
const int cols = virtualWidth(); const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
const int rows = virtualHeight(); const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
if (!delta || abs(delta) >= rows) return; int absDelta = abs(delta);
uint32_t newPxCol[rows]; if (absDelta >= vH) return;
for (int x = 0; x < cols; x++) { uint32_t newPxCol[vH];
if (delta > 0) { int newDelta;
for (int y = 0; y < rows-delta; y++) newPxCol[y] = getPixelColorXY(x, (y + delta)); int stop = vH;
for (int y = rows-delta; y < rows; y++) newPxCol[y] = getPixelColorXY(x, wrap ? (y + delta) - rows : y); int start = 0;
} else { if (wrap) newDelta = (delta + vH) % vH; // +rows in case delta < 0
for (int y = rows-1; y >= -delta; y--) newPxCol[y] = getPixelColorXY(x, (y + delta)); else {
for (int y = -delta-1; y >= 0; y--) newPxCol[y] = getPixelColorXY(x, wrap ? (y + delta) + rows : y); if (delta < 0) start = absDelta;
stop = vH - absDelta;
newDelta = delta > 0 ? delta : 0;
} }
for (int y = 0; y < rows; y++) setPixelColorXY(x, y, newPxCol[y]); for (int x = 0; x < vW; x++) {
for (int y = 0; y < stop; y++) {
int srcY = y + newDelta;
if (wrap) srcY %= vH; // Wrap using modulo when `wrap` is true
newPxCol[y] = getPixelColorXY(x, srcY);
}
for (int y = 0; y < stop; y++) setPixelColorXY(x, y + start, newPxCol[y]);
} }
} }
@ -489,7 +514,7 @@ void Segment::moveY(int8_t delta, bool wrap) {
// @param dir direction: 0=left, 1=left-up, 2=up, 3=right-up, 4=right, 5=right-down, 6=down, 7=left-down // @param dir direction: 0=left, 1=left-up, 2=up, 3=right-up, 4=right, 5=right-down, 6=down, 7=left-down
// @param delta number of pixels to move // @param delta number of pixels to move
// @param wrap around // @param wrap around
void Segment::move(uint8_t dir, uint8_t delta, bool wrap) { void Segment::move(unsigned dir, unsigned delta, bool wrap) {
if (delta==0) return; if (delta==0) return;
switch (dir) { switch (dir) {
case 0: moveX( delta, wrap); break; case 0: moveX( delta, wrap); break;
@ -507,46 +532,49 @@ void Segment::drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col,
if (!isActive() || radius == 0) return; // not active if (!isActive() || radius == 0) return; // not active
if (soft) { if (soft) {
// Xiaolin Wus algorithm // Xiaolin Wus algorithm
int rsq = radius*radius; const int rsq = radius*radius;
int x = 0; int x = 0;
int y = radius; int y = radius;
unsigned oldFade = 0; unsigned oldFade = 0;
while (x < y) { while (x < y) {
float yf = sqrtf(float(rsq - x*x)); // needs to be floating point float yf = sqrtf(float(rsq - x*x)); // needs to be floating point
unsigned fade = float(0xFFFF) * (ceilf(yf) - yf); // how much color to keep uint8_t fade = float(0xFF) * (ceilf(yf) - yf); // how much color to keep
if (oldFade > fade) y--; if (oldFade > fade) y--;
oldFade = fade; oldFade = fade;
setPixelColorXY(cx+x, cy+y, color_blend(col, getPixelColorXY(cx+x, cy+y), fade, true)); int px, py;
setPixelColorXY(cx-x, cy+y, color_blend(col, getPixelColorXY(cx-x, cy+y), fade, true)); for (uint8_t i = 0; i < 16; i++) {
setPixelColorXY(cx+x, cy-y, color_blend(col, getPixelColorXY(cx+x, cy-y), fade, true)); int swaps = (i & 0x4 ? 1 : 0); // 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1
setPixelColorXY(cx-x, cy-y, color_blend(col, getPixelColorXY(cx-x, cy-y), fade, true)); int adj = (i < 8) ? 0 : 1; // 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1
setPixelColorXY(cx+y, cy+x, color_blend(col, getPixelColorXY(cx+y, cy+x), fade, true)); int dx = (i & 1) ? -1 : 1; // 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1
setPixelColorXY(cx-y, cy+x, color_blend(col, getPixelColorXY(cx-y, cy+x), fade, true)); int dy = (i & 2) ? -1 : 1; // 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1
setPixelColorXY(cx+y, cy-x, color_blend(col, getPixelColorXY(cx+y, cy-x), fade, true)); if (swaps) {
setPixelColorXY(cx-y, cy-x, color_blend(col, getPixelColorXY(cx-y, cy-x), fade, true)); px = cx + (y - adj) * dx;
setPixelColorXY(cx+x, cy+y-1, color_blend(getPixelColorXY(cx+x, cy+y-1), col, fade, true)); py = cy + x * dy;
setPixelColorXY(cx-x, cy+y-1, color_blend(getPixelColorXY(cx-x, cy+y-1), col, fade, true)); } else {
setPixelColorXY(cx+x, cy-y+1, color_blend(getPixelColorXY(cx+x, cy-y+1), col, fade, true)); px = cx + x * dx;
setPixelColorXY(cx-x, cy-y+1, color_blend(getPixelColorXY(cx-x, cy-y+1), col, fade, true)); py = cy + (y - adj) * dy;
setPixelColorXY(cx+y-1, cy+x, color_blend(getPixelColorXY(cx+y-1, cy+x), col, fade, true)); }
setPixelColorXY(cx-y+1, cy+x, color_blend(getPixelColorXY(cx-y+1, cy+x), col, fade, true)); uint32_t pixCol = getPixelColorXY(px, py);
setPixelColorXY(cx+y-1, cy-x, color_blend(getPixelColorXY(cx+y-1, cy-x), col, fade, true)); setPixelColorXY(px, py, adj ?
setPixelColorXY(cx-y+1, cy-x, color_blend(getPixelColorXY(cx-y+1, cy-x), col, fade, true)); color_blend(pixCol, col, fade) :
color_blend(col, pixCol, fade));
}
x++; x++;
} }
} else { } else {
// pre-scale color for all pixels
col = color_fade(col, _segBri);
_colorScaled = true;
// Bresenhams Algorithm // Bresenhams Algorithm
int d = 3 - (2*radius); int d = 3 - (2*radius);
int y = radius, x = 0; int y = radius, x = 0;
while (y >= x) { while (y >= x) {
setPixelColorXY(cx+x, cy+y, col); for (int i = 0; i < 4; i++) {
setPixelColorXY(cx-x, cy+y, col); int dx = (i & 1) ? -x : x;
setPixelColorXY(cx+x, cy-y, col); int dy = (i & 2) ? -y : y;
setPixelColorXY(cx-x, cy-y, col); setPixelColorXY(cx + dx, cy + dy, col);
setPixelColorXY(cx+y, cy+x, col); setPixelColorXY(cx + dy, cy + dx, col);
setPixelColorXY(cx-y, cy+x, col); }
setPixelColorXY(cx+y, cy-x, col);
setPixelColorXY(cx-y, cy-x, col);
x++; x++;
if (d > 0) { if (d > 0) {
y--; y--;
@ -555,33 +583,38 @@ void Segment::drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col,
d += 4 * x + 6; d += 4 * x + 6;
} }
} }
_colorScaled = false;
} }
} }
// by stepko, taken from https://editor.soulmatelights.com/gallery/573-blobs // by stepko, taken from https://editor.soulmatelights.com/gallery/573-blobs
void Segment::fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, bool soft) { void Segment::fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, bool soft) {
if (!isActive() || radius == 0) return; // not active if (!isActive() || radius == 0) 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)
// draw soft bounding circle // draw soft bounding circle
if (soft) drawCircle(cx, cy, radius, col, soft); if (soft) drawCircle(cx, cy, radius, col, soft);
// pre-scale color for all pixels
col = color_fade(col, _segBri);
_colorScaled = true;
// fill it // fill it
const int cols = virtualWidth();
const int rows = virtualHeight();
for (int y = -radius; y <= radius; y++) { for (int y = -radius; y <= radius; y++) {
for (int x = -radius; x <= radius; x++) { for (int x = -radius; x <= radius; x++) {
if (x * x + y * y <= radius * radius && if (x * x + y * y <= radius * radius &&
int(cx)+x>=0 && int(cy)+y>=0 && int(cx)+x >= 0 && int(cy)+y >= 0 &&
int(cx)+x<cols && int(cy)+y<rows) int(cx)+x < vW && int(cy)+y < vH)
setPixelColorXY(cx + x, cy + y, col); setPixelColorXY(cx + x, cy + y, col);
} }
} }
_colorScaled = false;
} }
//line function //line function
void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft) { void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft) {
if (!isActive()) return; // not active if (!isActive()) return; // not active
const int cols = virtualWidth(); const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
const int rows = virtualHeight(); const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
if (x0 >= cols || x1 >= cols || y0 >= rows || y1 >= rows) return; if (x0 >= vW || x1 >= vW || y0 >= vH || y1 >= vH) return;
const int dx = abs(x1-x0), sx = x0<x1 ? 1 : -1; // x distance & step const int dx = abs(x1-x0), sx = x0<x1 ? 1 : -1; // x distance & step
const int dy = abs(y1-y0), sy = y0<y1 ? 1 : -1; // y distance & step const int dy = abs(y1-y0), sy = y0<y1 ? 1 : -1; // y distance & step
@ -608,17 +641,20 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3
float gradient = x1-x0 == 0 ? 1.0f : float(y1-y0) / float(x1-x0); float gradient = x1-x0 == 0 ? 1.0f : float(y1-y0) / float(x1-x0);
float intersectY = y0; float intersectY = y0;
for (int x = x0; x <= x1; x++) { for (int x = x0; x <= x1; x++) {
unsigned keep = float(0xFFFF) * (intersectY-int(intersectY)); // how much color to keep uint8_t keep = float(0xFF) * (intersectY-int(intersectY)); // how much color to keep
unsigned seep = 0xFFFF - keep; // how much background to keep uint8_t seep = 0xFF - keep; // how much background to keep
int y = int(intersectY); int y = int(intersectY);
if (steep) std::swap(x,y); // temporaryly swap if steep if (steep) std::swap(x,y); // temporaryly swap if steep
// pixel coverage is determined by fractional part of y co-ordinate // pixel coverage is determined by fractional part of y co-ordinate
setPixelColorXY(x, y, color_blend(c, getPixelColorXY(x, y), keep, true)); setPixelColorXY(x, y, color_blend(c, getPixelColorXY(x, y), keep));
setPixelColorXY(x+int(steep), y+int(!steep), color_blend(c, getPixelColorXY(x+int(steep), y+int(!steep)), seep, true)); setPixelColorXY(x+int(steep), y+int(!steep), color_blend(c, getPixelColorXY(x+int(steep), y+int(!steep)), seep));
intersectY += gradient; intersectY += gradient;
if (steep) std::swap(x,y); // restore if steep if (steep) std::swap(x,y); // restore if steep
} }
} else { } else {
// pre-scale color for all pixels
c = color_fade(c, _segBri);
_colorScaled = true;
// Bresenham's algorithm // Bresenham's algorithm
int err = (dx>dy ? dx : -dy)/2; // error direction int err = (dx>dy ? dx : -dy)/2; // error direction
for (;;) { for (;;) {
@ -628,6 +664,7 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3
if (e2 >-dx) { err -= dy; x0 += sx; } if (e2 >-dx) { err -= dy; x0 += sx; }
if (e2 < dy) { err += dx; y0 += sy; } if (e2 < dy) { err += dx; y0 += sy; }
} }
_colorScaled = false;
} }
} }
@ -639,16 +676,15 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3
// draws a raster font character on canvas // draws a raster font character on canvas
// only supports: 4x6=24, 5x8=40, 5x12=60, 6x8=48 and 7x9=63 fonts ATM // only supports: 4x6=24, 5x8=40, 5x12=60, 6x8=48 and 7x9=63 fonts ATM
void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2, int8_t rotate) { void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2, int8_t rotate, bool usePalGrad) {
if (!isActive()) return; // not active if (!isActive()) return; // not active
if (chr < 32 || chr > 126) return; // only ASCII 32-126 supported if (chr < 32 || chr > 126) return; // only ASCII 32-126 supported
chr -= 32; // align with font table entries chr -= 32; // align with font table entries
const int cols = virtualWidth();
const int rows = virtualHeight();
const int font = w*h; const int font = w*h;
CRGB col = CRGB(color); CRGB col = CRGB(color);
CRGBPalette16 grad = CRGBPalette16(col, col2 ? CRGB(col2) : col); CRGBPalette16 grad = CRGBPalette16(col, col2 ? CRGB(col2) : col);
if(usePalGrad) grad = SEGPALETTE; // selected palette as gradient
//if (w<5 || w>6 || h!=8) return; //if (w<5 || w>6 || h!=8) return;
for (int i = 0; i<h; i++) { // character height for (int i = 0; i<h; i++) { // character height
@ -661,7 +697,10 @@ void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w,
case 60: bits = pgm_read_byte_near(&console_font_5x12[(chr * h) + i]); break; // 5x12 font case 60: bits = pgm_read_byte_near(&console_font_5x12[(chr * h) + i]); break; // 5x12 font
default: return; default: return;
} }
col = ColorFromPalette(grad, (i+1)*255/h, 255, NOBLEND); uint32_t c = ColorFromPaletteWLED(grad, (i+1)*255/h, 255, NOBLEND);
// pre-scale color for all pixels
c = color_fade(c, _segBri);
_colorScaled = true;
for (int j = 0; j<w; j++) { // character width for (int j = 0; j<w; j++) { // character width
int x0, y0; int x0, y0;
switch (rotate) { switch (rotate) {
@ -671,11 +710,12 @@ void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w,
case 1: x0 = x + i; y0 = y + j; break; // +90 deg case 1: x0 = x + i; y0 = y + j; break; // +90 deg
default: x0 = x + (w-1) - j; y0 = y + i; break; // no rotation default: x0 = x + (w-1) - j; y0 = y + i; break; // no rotation
} }
if (x0 < 0 || x0 >= cols || y0 < 0 || y0 >= rows) continue; // drawing off-screen if (x0 < 0 || x0 >= (int)vWidth() || y0 < 0 || y0 >= (int)vHeight()) continue; // drawing off-screen
if (((bits>>(j+(8-w))) & 0x01)) { // bit set if (((bits>>(j+(8-w))) & 0x01)) { // bit set
setPixelColorXY(x0, y0, col); setPixelColorXY(x0, y0, c);
} }
} }
_colorScaled = false;
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -126,10 +126,10 @@ void onAlexaChange(EspalexaDevice* dev)
} else { } else {
colorKtoRGB(k, rgbw); colorKtoRGB(k, rgbw);
} }
strip.setColor(0, RGBW32(rgbw[0], rgbw[1], rgbw[2], rgbw[3])); strip.getMainSegment().setColor(0, RGBW32(rgbw[0], rgbw[1], rgbw[2], rgbw[3]));
} else { } else {
uint32_t color = dev->getRGB(); uint32_t color = dev->getRGB();
strip.setColor(0, color); strip.getMainSegment().setColor(0, color);
} }
stateUpdated(CALL_MODE_ALEXA); stateUpdated(CALL_MODE_ALEXA);
} }

View File

@ -16,6 +16,9 @@
#define LEDC_MUTEX_UNLOCK() #define LEDC_MUTEX_UNLOCK()
#endif #endif
#endif #endif
#ifdef ESP8266
#include "core_esp8266_waveform.h"
#endif
#include "const.h" #include "const.h"
#include "pin_manager.h" #include "pin_manager.h"
#include "bus_wrapper.h" #include "bus_wrapper.h"
@ -27,7 +30,7 @@ extern bool cctICused;
uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb); uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
//udp.cpp //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 // enable additional debug output
#if defined(WLED_DEBUG_HOST) #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)) : Bus(bc.type, bc.start, bc.autoWhite, bc.count, bc.reversed, (bc.refreshReq || bc.type == TYPE_TM1814))
, _skip(bc.skipAmount) //sacrificial pixels , _skip(bc.skipAmount) //sacrificial pixels
, _colorOrder(bc.colorOrder) , _colorOrder(bc.colorOrder)
@ -150,21 +153,11 @@ BusDigital::BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com)
//_buffering = bc.doubleBuffer; //_buffering = bc.doubleBuffer;
uint16_t lenToCreate = bc.count; uint16_t lenToCreate = bc.count;
if (bc.type == TYPE_WS2812_1CH_X3) lenToCreate = NUM_ICS_WS2812_1CH_3X(bc.count); // only needs a third of "RGB" LEDs for NeoPixelBus if (bc.type == TYPE_WS2812_1CH_X3) lenToCreate = NUM_ICS_WS2812_1CH_3X(bc.count); // only needs a third of "RGB" LEDs for NeoPixelBus
_busPtr = PolyBus::create(_iType, _pins, lenToCreate + _skip, nr, _frequencykHz); _busPtr = PolyBus::create(_iType, _pins, lenToCreate + _skip, nr);
_valid = (_busPtr != nullptr); _valid = (_busPtr != nullptr);
DEBUG_PRINTF_P(PSTR("%successfully inited strip %u (len %u) with type %u and pins %u,%u (itype %u). mA=%d/%d\n"), _valid?"S":"Uns", nr, bc.count, bc.type, _pins[0], is2Pin(bc.type)?_pins[1]:255, _iType, _milliAmpsPerLed, _milliAmpsMax); DEBUG_PRINTF_P(PSTR("%successfully inited strip %u (len %u) with type %u and pins %u,%u (itype %u). mA=%d/%d\n"), _valid?"S":"Uns", nr, bc.count, bc.type, _pins[0], is2Pin(bc.type)?_pins[1]:255, _iType, _milliAmpsPerLed, _milliAmpsMax);
} }
//fine tune power estimation constants for your setup
//you can set it to 0 if the ESP is powered by USB and the LEDs by external
#ifndef MA_FOR_ESP
#ifdef ESP8266
#define MA_FOR_ESP 80 //how much mA does the ESP use (Wemos D1 about 80mA)
#else
#define MA_FOR_ESP 120 //how much mA does the ESP use (ESP32 about 120mA)
#endif
#endif
//DISCLAIMER //DISCLAIMER
//The following function attemps to calculate the current LED power usage, //The following function attemps to calculate the current LED power usage,
//and will limit the brightness to stay below a set amperage threshold. //and will limit the brightness to stay below a set amperage threshold.
@ -306,22 +299,22 @@ void BusDigital::setStatusPixel(uint32_t c) {
} }
} }
void IRAM_ATTR BusDigital::setPixelColor(uint16_t pix, uint32_t c) { void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) {
if (!_valid) return; if (!_valid) return;
uint8_t cctWW = 0, cctCW = 0;
if (hasWhite()) c = autoWhiteCalc(c); if (hasWhite()) c = autoWhiteCalc(c);
if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT
if (_data) { if (_data) {
size_t offset = pix * getNumberOfChannels(); size_t offset = pix * getNumberOfChannels();
uint8_t* dataptr = _data + offset;
if (hasRGB()) { if (hasRGB()) {
_data[offset++] = R(c); *dataptr++ = R(c);
_data[offset++] = G(c); *dataptr++ = G(c);
_data[offset++] = B(c); *dataptr++ = B(c);
} }
if (hasWhite()) _data[offset++] = W(c); if (hasWhite()) *dataptr++ = W(c);
// unfortunately as a segment may span multiple buses or a bus may contain multiple segments and each segment may have different CCT // unfortunately as a segment may span multiple buses or a bus may contain multiple segments and each segment may have different CCT
// we need to store CCT value for each pixel (if there is a color correction in play, convert K in CCT ratio) // we need to store CCT value for each pixel (if there is a color correction in play, convert K in CCT ratio)
if (hasCCT()) _data[offset] = Bus::_cct >= 1900 ? (Bus::_cct - 1900) >> 5 : (Bus::_cct < 0 ? 127 : Bus::_cct); // TODO: if _cct == -1 we simply ignore it if (hasCCT()) *dataptr = Bus::_cct >= 1900 ? (Bus::_cct - 1900) >> 5 : (Bus::_cct < 0 ? 127 : Bus::_cct); // TODO: if _cct == -1 we simply ignore it
} else { } else {
if (_reversed) pix = _len - pix -1; if (_reversed) pix = _len - pix -1;
pix += _skip; pix += _skip;
@ -336,16 +329,22 @@ void IRAM_ATTR BusDigital::setPixelColor(uint16_t pix, uint32_t c) {
case 2: c = RGBW32(R(cOld), G(cOld), W(c) , 0); break; case 2: c = RGBW32(R(cOld), G(cOld), W(c) , 0); break;
} }
} }
if (hasCCT()) Bus::calculateCCT(c, cctWW, cctCW); uint16_t wwcw = 0;
PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, (cctCW<<8) | cctWW); if (hasCCT()) {
uint8_t cctWW = 0, cctCW = 0;
Bus::calculateCCT(c, cctWW, cctCW);
wwcw = (cctCW<<8) | cctWW;
}
PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, wwcw);
} }
} }
// returns original color if global buffering is enabled, else returns lossly restored color from bus // returns original color if global buffering is enabled, else returns lossly restored color from bus
uint32_t IRAM_ATTR BusDigital::getPixelColor(uint16_t pix) const { uint32_t IRAM_ATTR BusDigital::getPixelColor(unsigned pix) const {
if (!_valid) return 0; if (!_valid) return 0;
if (_data) { if (_data) {
size_t offset = pix * getNumberOfChannels(); const size_t offset = pix * getNumberOfChannels();
uint32_t c; uint32_t c;
if (!hasRGB()) { if (!hasRGB()) {
c = RGBW32(_data[offset], _data[offset], _data[offset], _data[offset]); c = RGBW32(_data[offset], _data[offset], _data[offset], _data[offset]);
@ -356,7 +355,7 @@ uint32_t IRAM_ATTR BusDigital::getPixelColor(uint16_t pix) const {
} else { } else {
if (_reversed) pix = _len - pix -1; if (_reversed) pix = _len - pix -1;
pix += _skip; pix += _skip;
unsigned co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder); const unsigned co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder);
uint32_t c = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, (_type==TYPE_WS2812_1CH_X3) ? IC_INDEX_WS2812_1CH_3X(pix) : pix, co),_bri); uint32_t c = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, (_type==TYPE_WS2812_1CH_X3) ? IC_INDEX_WS2812_1CH_3X(pix) : pix, co),_bri);
if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs
unsigned r = R(c); unsigned r = R(c);
@ -410,9 +409,9 @@ std::vector<LEDType> BusDigital::getLEDTypes() {
}; };
} }
void BusDigital::reinit() { void BusDigital::begin() {
if (!_valid) return; if (!_valid) return;
PolyBus::begin(_busPtr, _iType, _pins); PolyBus::begin(_busPtr, _iType, _pins, _frequencykHz);
} }
void BusDigital::cleanup() { void BusDigital::cleanup() {
@ -452,7 +451,7 @@ void BusDigital::cleanup() {
#endif #endif
#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 : 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; if (!isPWM(bc.type)) return;
@ -466,10 +465,7 @@ BusPwm::BusPwm(BusConfig &bc)
for (unsigned i = 0; i < numPins; i++) pins[i] = {(int8_t)bc.pins[i], true}; for (unsigned i = 0; i < numPins; i++) pins[i] = {(int8_t)bc.pins[i], true};
if (!PinManager::allocateMultiplePins(pins, numPins, PinOwner::BusPwm)) return; if (!PinManager::allocateMultiplePins(pins, numPins, PinOwner::BusPwm)) return;
#ifdef ESP8266 #ifdef ARDUINO_ARCH_ESP32
analogWriteRange((1<<_depth)-1);
analogWriteFreq(_frequency);
#else
// for 2 pin PWM CCT strip pinManager will make sure both LEDC channels are in the same speed group and sharing the same timer // 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); _ledcStart = PinManager::allocateLedc(numPins);
if (_ledcStart == 255) { //no more free LEDC channels if (_ledcStart == 255) { //no more free LEDC channels
@ -501,7 +497,7 @@ BusPwm::BusPwm(BusConfig &bc)
DEBUG_PRINTF_P(PSTR("%successfully inited PWM strip with type %u, frequency %u, bit depth %u and pins %u,%u,%u,%u,%u\n"), _valid?"S":"Uns", bc.type, _frequency, _depth, _pins[0], _pins[1], _pins[2], _pins[3], _pins[4]); DEBUG_PRINTF_P(PSTR("%successfully inited PWM strip with type %u, frequency %u, bit depth %u and pins %u,%u,%u,%u,%u\n"), _valid?"S":"Uns", bc.type, _frequency, _depth, _pins[0], _pins[1], _pins[2], _pins[3], _pins[4]);
} }
void BusPwm::setPixelColor(uint16_t pix, uint32_t c) { void BusPwm::setPixelColor(unsigned pix, uint32_t c) {
if (pix != 0 || !_valid) return; //only react to first pixel if (pix != 0 || !_valid) return; //only react to first pixel
if (_type != TYPE_ANALOG_3CH) c = autoWhiteCalc(c); if (_type != TYPE_ANALOG_3CH) c = autoWhiteCalc(c);
if (Bus::_cct >= 1900 && (_type == TYPE_ANALOG_3CH || _type == TYPE_ANALOG_4CH)) { if (Bus::_cct >= 1900 && (_type == TYPE_ANALOG_3CH || _type == TYPE_ANALOG_4CH)) {
@ -538,7 +534,7 @@ void BusPwm::setPixelColor(uint16_t pix, uint32_t c) {
} }
//does no index check //does no index check
uint32_t BusPwm::getPixelColor(uint16_t pix) const { uint32_t BusPwm::getPixelColor(unsigned pix) const {
if (!_valid) return 0; if (!_valid) return 0;
// TODO getting the reverse from CCT is involved (a quick approximation when CCT blending is ste to 0 implemented) // TODO getting the reverse from CCT is involved (a quick approximation when CCT blending is ste to 0 implemented)
switch (_type) { switch (_type) {
@ -560,26 +556,28 @@ uint32_t BusPwm::getPixelColor(uint16_t pix) const {
void BusPwm::show() { void BusPwm::show() {
if (!_valid) return; 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) // 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) // https://github.com/Aircoookie/WLED/pull/4115 and https://github.com/zalatnaicsongor/WLED/pull/1)
const bool dithering = _needsRefresh; // avoid working with bitfield 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) 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 (cubic) to fit (or approximate linearity of) human eye perceived brightness // use CIE brightness formula (linear + cubic) to approximate human eye perceived brightness
// the formula is based on 12 bit resolution as there is no need for greater precision
// see: https://en.wikipedia.org/wiki/Lightness // see: https://en.wikipedia.org/wiki/Lightness
unsigned pwmBri = (unsigned)_bri * 100; // enlarge to use integer math for linear response unsigned pwmBri = _bri;
if (pwmBri < 2040) { if (pwmBri < 21) { // linear response for values [0-20]
// linear response for values [0-20] pwmBri = (pwmBri * maxBri + 2300 / 2) / 2300 ; // adding '0.5' before division for correct rounding, 2300 gives a good match to CIE curve
pwmBri = ((pwmBri << 12) + 115043) / 230087; //adding '0.5' before division for correct rounding } else { // cubic response for values [21-255]
} else { float temp = float(pwmBri + 41) / float(255 + 41); // 41 is to match offset & slope to linear part
// cubic response for values [21-255]
pwmBri += 4080;
float temp = (float)pwmBri / 29580.0f;
temp = temp * temp * temp * (float)maxBri; temp = temp * temp * temp * (float)maxBri;
pwmBri = (unsigned)temp; // pwmBri is in range [0-maxBri] pwmBri = (unsigned)temp; // pwmBri is in range [0-maxBri] C
} }
[[maybe_unused]] unsigned hPoint = 0; // phase shift (0 - maxBri) [[maybe_unused]] unsigned hPoint = 0; // phase shift (0 - maxBri)
@ -591,19 +589,24 @@ void BusPwm::show() {
// also mandatory that both channels use the same timer (pinManager takes care of that). // also mandatory that both channels use the same timer (pinManager takes care of that).
for (unsigned i = 0; i < numPins; i++) { for (unsigned i = 0; i < numPins; i++) {
unsigned duty = (_data[i] * pwmBri) / 255; unsigned duty = (_data[i] * pwmBri) / 255;
#ifdef ESP8266 unsigned deadTime = 0;
if (_reversed) duty = maxBri - duty;
analogWrite(_pins[i], duty);
#else
int deadTime = 0;
if (_type == TYPE_ANALOG_2CH && Bus::getCCTBlend() == 0) { if (_type == TYPE_ANALOG_2CH && Bus::getCCTBlend() == 0) {
// add dead time between signals (when using dithering, two full 8bit pulses are required) // add dead time between signals (when using dithering, two full 8bit pulses are required)
deadTime = (1+dithering) << bitShift; deadTime = (1+dithering) << bitShift;
// we only need to take care of shortening the signal at (almost) full brightness otherwise pulses may overlap // 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 (_bri >= 254 && duty >= maxBri / 2 && duty < maxBri) {
if (_reversed) deadTime = -deadTime; // need to invert dead time to make phaseshift go the opposite way so low signals dont overlap 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 channel = _ledcStart + i;
unsigned gr = channel/8; // high/low speed group unsigned gr = channel/8; // high/low speed group
unsigned ch = channel%8; // group channel unsigned ch = channel%8; // group channel
@ -612,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].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.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); 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 #endif
if (!_reversed) hPoint += duty;
hPoint += deadTime; // offset to cascade the signals
if (hPoint >= maxBri) hPoint -= maxBri; // offset is out of bounds, reset
} }
} }
@ -654,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) : Bus(bc.type, bc.start, bc.autoWhite, 1, bc.reversed)
, _onoffdata(0) , _onoffdata(0)
{ {
@ -674,7 +679,7 @@ BusOnOff::BusOnOff(BusConfig &bc)
DEBUG_PRINTF_P(PSTR("%successfully inited On/Off strip with pin %u\n"), _valid?"S":"Uns", _pin); DEBUG_PRINTF_P(PSTR("%successfully inited On/Off strip with pin %u\n"), _valid?"S":"Uns", _pin);
} }
void BusOnOff::setPixelColor(uint16_t pix, uint32_t c) { void BusOnOff::setPixelColor(unsigned pix, uint32_t c) {
if (pix != 0 || !_valid) return; //only react to first pixel if (pix != 0 || !_valid) return; //only react to first pixel
c = autoWhiteCalc(c); c = autoWhiteCalc(c);
uint8_t r = R(c); uint8_t r = R(c);
@ -684,7 +689,7 @@ void BusOnOff::setPixelColor(uint16_t pix, uint32_t c) {
_data[0] = bool(r|g|b|w) && bool(_bri) ? 0xFF : 0; _data[0] = bool(r|g|b|w) && bool(_bri) ? 0xFF : 0;
} }
uint32_t BusOnOff::getPixelColor(uint16_t pix) const { uint32_t BusOnOff::getPixelColor(unsigned pix) const {
if (!_valid) return 0; if (!_valid) return 0;
return RGBW32(_data[0], _data[0], _data[0], _data[0]); return RGBW32(_data[0], _data[0], _data[0], _data[0]);
} }
@ -707,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) : Bus(bc.type, bc.start, bc.autoWhite, bc.count)
, _broadcastLock(false) , _broadcastLock(false)
{ {
@ -734,7 +739,7 @@ BusNetwork::BusNetwork(BusConfig &bc)
DEBUG_PRINTF_P(PSTR("%successfully inited virtual strip with type %u and IP %u.%u.%u.%u\n"), _valid?"S":"Uns", bc.type, bc.pins[0], bc.pins[1], bc.pins[2], bc.pins[3]); DEBUG_PRINTF_P(PSTR("%successfully inited virtual strip with type %u and IP %u.%u.%u.%u\n"), _valid?"S":"Uns", bc.type, bc.pins[0], bc.pins[1], bc.pins[2], bc.pins[3]);
} }
void BusNetwork::setPixelColor(uint16_t pix, uint32_t c) { void BusNetwork::setPixelColor(unsigned pix, uint32_t c) {
if (!_valid || pix >= _len) return; if (!_valid || pix >= _len) return;
if (_hasWhite) c = autoWhiteCalc(c); if (_hasWhite) c = autoWhiteCalc(c);
if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT
@ -745,7 +750,7 @@ void BusNetwork::setPixelColor(uint16_t pix, uint32_t c) {
if (_hasWhite) _data[offset+3] = W(c); if (_hasWhite) _data[offset+3] = W(c);
} }
uint32_t BusNetwork::getPixelColor(uint16_t pix) const { uint32_t BusNetwork::getPixelColor(unsigned pix) const {
if (!_valid || pix >= _len) return 0; if (!_valid || pix >= _len) return 0;
unsigned offset = pix * _UDPchannels; unsigned offset = pix * _UDPchannels;
return RGBW32(_data[offset], _data[offset+1], _data[offset+2], (hasWhite() ? _data[offset+3] : 0)); return RGBW32(_data[offset], _data[offset+1], _data[offset+2], (hasWhite() ? _data[offset+3] : 0));
@ -786,7 +791,7 @@ void BusNetwork::cleanup() {
//utility to get the approx. memory usage of a given BusConfig //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; if (Bus::isOnOff(bc.type) || Bus::isPWM(bc.type)) return OUTPUT_MAX_PINS;
unsigned len = bc.count + bc.skipAmount; unsigned len = bc.count + bc.skipAmount;
@ -811,7 +816,7 @@ uint32_t BusManager::memUsage(unsigned maxChannels, unsigned maxCount, unsigned
return (maxChannels * maxCount * minBuses * multiplier); 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 (getNumBusses() - getNumVirtualBusses() >= WLED_MAX_BUSSES) return -1;
if (Bus::isVirtual(bc.type)) { if (Bus::isVirtual(bc.type)) {
busses[numBusses] = new BusNetwork(bc); busses[numBusses] = new BusNetwork(bc);
@ -910,7 +915,7 @@ void BusManager::on() {
if (busses[i]->isDigital() && busses[i]->getPins(pins)) { if (busses[i]->isDigital() && busses[i]->getPins(pins)) {
if (pins[0] == LED_BUILTIN || pins[1] == LED_BUILTIN) { if (pins[0] == LED_BUILTIN || pins[1] == LED_BUILTIN) {
BusDigital *bus = static_cast<BusDigital*>(busses[i]); BusDigital *bus = static_cast<BusDigital*>(busses[i]);
bus->reinit(); bus->begin();
break; break;
} }
} }
@ -943,7 +948,6 @@ void BusManager::show() {
busses[i]->show(); busses[i]->show();
_milliAmpsUsed += busses[i]->getUsedCurrent(); _milliAmpsUsed += busses[i]->getUsedCurrent();
} }
if (_milliAmpsUsed) _milliAmpsUsed += MA_FOR_ESP;
} }
void BusManager::setStatusPixel(uint32_t c) { void BusManager::setStatusPixel(uint32_t c) {
@ -952,7 +956,7 @@ void BusManager::setStatusPixel(uint32_t c) {
} }
} }
void IRAM_ATTR BusManager::setPixelColor(uint16_t pix, uint32_t c) { void IRAM_ATTR BusManager::setPixelColor(unsigned pix, uint32_t c) {
for (unsigned i = 0; i < numBusses; i++) { for (unsigned i = 0; i < numBusses; i++) {
unsigned bstart = busses[i]->getStart(); unsigned bstart = busses[i]->getStart();
if (pix < bstart || pix >= bstart + busses[i]->getLength()) continue; if (pix < bstart || pix >= bstart + busses[i]->getLength()) continue;
@ -975,7 +979,7 @@ void BusManager::setSegmentCCT(int16_t cct, bool allowWBCorrection) {
Bus::setCCT(cct); Bus::setCCT(cct);
} }
uint32_t BusManager::getPixelColor(uint16_t pix) { uint32_t BusManager::getPixelColor(unsigned pix) {
for (unsigned i = 0; i < numBusses; i++) { for (unsigned i = 0; i < numBusses; i++) {
unsigned bstart = busses[i]->getStart(); unsigned bstart = busses[i]->getStart();
if (!busses[i]->containsPixel(pix)) continue; if (!busses[i]->containsPixel(pix)) continue;

View File

@ -6,6 +6,7 @@
*/ */
#include "const.h" #include "const.h"
#include "pin_manager.h"
#include <vector> #include <vector>
//colors.cpp //colors.cpp
@ -79,13 +80,14 @@ class Bus {
virtual ~Bus() {} //throw the bus under the bus virtual ~Bus() {} //throw the bus under the bus
virtual void begin() {};
virtual void show() = 0; virtual void show() = 0;
virtual bool canShow() const { return true; } virtual bool canShow() const { return true; }
virtual void setStatusPixel(uint32_t c) {} virtual void setStatusPixel(uint32_t c) {}
virtual void setPixelColor(uint16_t pix, uint32_t c) = 0; virtual void setPixelColor(unsigned pix, uint32_t c) = 0;
virtual void setBrightness(uint8_t b) { _bri = b; }; virtual void setBrightness(uint8_t b) { _bri = b; };
virtual void setColorOrder(uint8_t co) {} virtual void setColorOrder(uint8_t co) {}
virtual uint32_t getPixelColor(uint16_t pix) const { return 0; } virtual uint32_t getPixelColor(unsigned pix) const { return 0; }
virtual uint8_t getPins(uint8_t* pinArray = nullptr) const { return 0; } virtual uint8_t getPins(uint8_t* pinArray = nullptr) const { return 0; }
virtual uint16_t getLength() const { return isOk() ? _len : 0; } virtual uint16_t getLength() const { return isOk() ? _len : 0; }
virtual uint8_t getColorOrder() const { return COL_ORDER_RGB; } virtual uint8_t getColorOrder() const { return COL_ORDER_RGB; }
@ -109,7 +111,7 @@ class Bus {
inline void setStart(uint16_t start) { _start = start; } inline void setStart(uint16_t start) { _start = start; }
inline void setAutoWhiteMode(uint8_t m) { if (m < 5) _autoWhiteMode = m; } inline void setAutoWhiteMode(uint8_t m) { if (m < 5) _autoWhiteMode = m; }
inline uint8_t getAutoWhiteMode() const { return _autoWhiteMode; } inline uint8_t getAutoWhiteMode() const { return _autoWhiteMode; }
inline uint8_t getNumberOfChannels() const { return hasWhite() + 3*hasRGB() + hasCCT(); } inline uint32_t getNumberOfChannels() const { return hasWhite() + 3*hasRGB() + hasCCT(); }
inline uint16_t getStart() const { return _start; } inline uint16_t getStart() const { return _start; }
inline uint8_t getType() const { return _type; } inline uint8_t getType() const { return _type; }
inline bool isOk() const { return _valid; } inline bool isOk() const { return _valid; }
@ -118,8 +120,8 @@ class Bus {
inline bool containsPixel(uint16_t pix) const { return pix >= _start && pix < _start + _len; } inline bool containsPixel(uint16_t pix) const { return pix >= _start && pix < _start + _len; }
static inline std::vector<LEDType> getLEDTypes() { return {{TYPE_NONE, "", PSTR("None")}}; } // not used. just for reference for derived classes static inline std::vector<LEDType> getLEDTypes() { return {{TYPE_NONE, "", PSTR("None")}}; } // not used. just for reference for derived classes
static constexpr uint8_t getNumberOfPins(uint8_t type) { return isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : is2Pin(type) + 1; } // credit @PaoloTK static constexpr uint32_t getNumberOfPins(uint8_t type) { return isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : is2Pin(type) + 1; } // credit @PaoloTK
static constexpr uint8_t getNumberOfChannels(uint8_t type) { return hasWhite(type) + 3*hasRGB(type) + hasCCT(type); } static constexpr uint32_t getNumberOfChannels(uint8_t type) { return hasWhite(type) + 3*hasRGB(type) + hasCCT(type); }
static constexpr bool hasRGB(uint8_t type) { static constexpr bool hasRGB(uint8_t type) {
return !((type >= TYPE_WS2812_1CH && type <= TYPE_WS2812_WWA) || type == TYPE_ANALOG_1CH || type == TYPE_ANALOG_2CH || type == TYPE_ONOFF); return !((type >= TYPE_WS2812_1CH && type <= TYPE_WS2812_WWA) || type == TYPE_ANALOG_1CH || type == TYPE_ANALOG_2CH || type == TYPE_ONOFF);
} }
@ -196,16 +198,16 @@ class Bus {
class BusDigital : public Bus { class BusDigital : public Bus {
public: public:
BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com); BusDigital(const BusConfig &bc, uint8_t nr, const ColorOrderMap &com);
~BusDigital() { cleanup(); } ~BusDigital() { cleanup(); }
void show() override; void show() override;
bool canShow() const override; bool canShow() const override;
void setBrightness(uint8_t b) override; void setBrightness(uint8_t b) override;
void setStatusPixel(uint32_t c) override; void setStatusPixel(uint32_t c) override;
[[gnu::hot]] void setPixelColor(uint16_t pix, uint32_t c) override; [[gnu::hot]] void setPixelColor(unsigned pix, uint32_t c) override;
void setColorOrder(uint8_t colorOrder) override; void setColorOrder(uint8_t colorOrder) override;
[[gnu::hot]] uint32_t getPixelColor(uint16_t pix) const override; [[gnu::hot]] uint32_t getPixelColor(unsigned pix) const override;
uint8_t getColorOrder() const override { return _colorOrder; } uint8_t getColorOrder() const override { return _colorOrder; }
uint8_t getPins(uint8_t* pinArray = nullptr) const override; uint8_t getPins(uint8_t* pinArray = nullptr) const override;
uint8_t skippedLeds() const override { return _skip; } uint8_t skippedLeds() const override { return _skip; }
@ -213,7 +215,7 @@ class BusDigital : public Bus {
uint16_t getLEDCurrent() const override { return _milliAmpsPerLed; } uint16_t getLEDCurrent() const override { return _milliAmpsPerLed; }
uint16_t getUsedCurrent() const override { return _milliAmpsTotal; } uint16_t getUsedCurrent() const override { return _milliAmpsTotal; }
uint16_t getMaxCurrent() const override { return _milliAmpsMax; } uint16_t getMaxCurrent() const override { return _milliAmpsMax; }
void reinit(); void begin() override;
void cleanup(); void cleanup();
static std::vector<LEDType> getLEDTypes(); static std::vector<LEDType> getLEDTypes();
@ -248,11 +250,11 @@ class BusDigital : public Bus {
class BusPwm : public Bus { class BusPwm : public Bus {
public: public:
BusPwm(BusConfig &bc); BusPwm(const BusConfig &bc);
~BusPwm() { cleanup(); } ~BusPwm() { cleanup(); }
void setPixelColor(uint16_t pix, uint32_t c) override; void setPixelColor(unsigned pix, uint32_t c) override;
uint32_t getPixelColor(uint16_t pix) const override; //does no index check uint32_t getPixelColor(unsigned pix) const override; //does no index check
uint8_t getPins(uint8_t* pinArray = nullptr) const override; uint8_t getPins(uint8_t* pinArray = nullptr) const override;
uint16_t getFrequency() const override { return _frequency; } uint16_t getFrequency() const override { return _frequency; }
void show() override; void show() override;
@ -275,11 +277,11 @@ class BusPwm : public Bus {
class BusOnOff : public Bus { class BusOnOff : public Bus {
public: public:
BusOnOff(BusConfig &bc); BusOnOff(const BusConfig &bc);
~BusOnOff() { cleanup(); } ~BusOnOff() { cleanup(); }
void setPixelColor(uint16_t pix, uint32_t c) override; void setPixelColor(unsigned pix, uint32_t c) override;
uint32_t getPixelColor(uint16_t pix) const override; uint32_t getPixelColor(unsigned pix) const override;
uint8_t getPins(uint8_t* pinArray) const override; uint8_t getPins(uint8_t* pinArray) const override;
void show() override; void show() override;
void cleanup() { PinManager::deallocatePin(_pin, PinOwner::BusOnOff); } void cleanup() { PinManager::deallocatePin(_pin, PinOwner::BusOnOff); }
@ -294,12 +296,12 @@ class BusOnOff : public Bus {
class BusNetwork : public Bus { class BusNetwork : public Bus {
public: public:
BusNetwork(BusConfig &bc); BusNetwork(const BusConfig &bc);
~BusNetwork() { cleanup(); } ~BusNetwork() { cleanup(); }
bool canShow() const override { return !_broadcastLock; } // this should be a return value from UDP routine if it is still sending data out bool canShow() const override { return !_broadcastLock; } // this should be a return value from UDP routine if it is still sending data out
void setPixelColor(uint16_t pix, uint32_t c) override; void setPixelColor(unsigned pix, uint32_t c) override;
uint32_t getPixelColor(uint16_t pix) const override; uint32_t getPixelColor(unsigned pix) const override;
uint8_t getPins(uint8_t* pinArray = nullptr) const override; uint8_t getPins(uint8_t* pinArray = nullptr) const override;
void show() override; void show() override;
void cleanup(); void cleanup();
@ -362,17 +364,27 @@ struct BusConfig {
}; };
//fine tune power estimation constants for your setup
//you can set it to 0 if the ESP is powered by USB and the LEDs by external
#ifndef MA_FOR_ESP
#ifdef ESP8266
#define MA_FOR_ESP 80 //how much mA does the ESP use (Wemos D1 about 80mA)
#else
#define MA_FOR_ESP 120 //how much mA does the ESP use (ESP32 about 120mA)
#endif
#endif
class BusManager { class BusManager {
public: public:
BusManager() {}; BusManager() {};
//utility to get the approx. memory usage of a given BusConfig //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 uint32_t memUsage(unsigned channels, unsigned count, unsigned buses = 1);
static uint16_t currentMilliamps() { return _milliAmpsUsed; } static uint16_t currentMilliamps() { return _milliAmpsUsed + MA_FOR_ESP; }
static uint16_t ablMilliampsMax() { return _milliAmpsMax; } static uint16_t ablMilliampsMax() { return _milliAmpsMax; }
static int add(BusConfig &bc); static int add(const BusConfig &bc);
static void useParallelOutput(); // workaround for inaccessible PolyBus static void useParallelOutput(); // workaround for inaccessible PolyBus
//do not call this method from system context (network callback) //do not call this method from system context (network callback)
@ -384,13 +396,13 @@ class BusManager {
static void show(); static void show();
static bool canAllShow(); static bool canAllShow();
static void setStatusPixel(uint32_t c); static void setStatusPixel(uint32_t c);
[[gnu::hot]] static void setPixelColor(uint16_t pix, uint32_t c); [[gnu::hot]] static void setPixelColor(unsigned pix, uint32_t c);
static void setBrightness(uint8_t b); static void setBrightness(uint8_t b);
// for setSegmentCCT(), cct can only be in [-1,255] range; allowWBCorrection will convert it to K // for setSegmentCCT(), cct can only be in [-1,255] range; allowWBCorrection will convert it to K
// WARNING: setSegmentCCT() is a misleading name!!! much better would be setGlobalCCT() or just setCCT() // WARNING: setSegmentCCT() is a misleading name!!! much better would be setGlobalCCT() or just setCCT()
static void setSegmentCCT(int16_t cct, bool allowWBCorrection = false); static void setSegmentCCT(int16_t cct, bool allowWBCorrection = false);
static inline void setMilliampsMax(uint16_t max) { _milliAmpsMax = max;} static inline void setMilliampsMax(uint16_t max) { _milliAmpsMax = max;}
static uint32_t getPixelColor(uint16_t pix); [[gnu::hot]] static uint32_t getPixelColor(unsigned pix);
static inline int16_t getSegmentCCT() { return Bus::getCCT(); } static inline int16_t getSegmentCCT() { return Bus::getCCT(); }
static Bus* getBus(uint8_t busNr); static Bus* getBus(uint8_t busNr);

View File

@ -336,7 +336,7 @@ class PolyBus {
// initialize SPI bus speed for DotStar methods // initialize SPI bus speed for DotStar methods
template <class T> template <class T>
static void beginDotStar(void* busPtr, int8_t sck, int8_t miso, int8_t mosi, int8_t ss, uint16_t clock_kHz = 0U) { static void beginDotStar(void* busPtr, int8_t sck, int8_t miso, int8_t mosi, int8_t ss, uint16_t clock_kHz /* 0 == use default */) {
T dotStar_strip = static_cast<T>(busPtr); T dotStar_strip = static_cast<T>(busPtr);
#ifdef ESP8266 #ifdef ESP8266
dotStar_strip->Begin(); dotStar_strip->Begin();
@ -363,7 +363,7 @@ class PolyBus {
tm1914_strip->SetPixelSettings(NeoTm1914Settings()); //NeoTm1914_Mode_DinFdinAutoSwitch, NeoTm1914_Mode_DinOnly, NeoTm1914_Mode_FdinOnly tm1914_strip->SetPixelSettings(NeoTm1914Settings()); //NeoTm1914_Mode_DinFdinAutoSwitch, NeoTm1914_Mode_DinOnly, NeoTm1914_Mode_FdinOnly
} }
static void begin(void* busPtr, uint8_t busType, uint8_t* pins, uint16_t clock_kHz = 0U) { static void begin(void* busPtr, uint8_t busType, uint8_t* pins, uint16_t clock_kHz /* only used by DotStar */) {
switch (busType) { switch (busType) {
case I_NONE: break; case I_NONE: break;
#ifdef ESP8266 #ifdef ESP8266
@ -480,7 +480,7 @@ class PolyBus {
} }
} }
static void* create(uint8_t busType, uint8_t* pins, uint16_t len, uint8_t channel, uint16_t clock_kHz = 0U) { static void* create(uint8_t busType, uint8_t* pins, uint16_t len, uint8_t channel) {
#if defined(ARDUINO_ARCH_ESP32) && !(defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3)) #if defined(ARDUINO_ARCH_ESP32) && !(defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3))
// NOTE: "channel" is only used on ESP32 (and its variants) for RMT channel allocation // NOTE: "channel" is only used on ESP32 (and its variants) for RMT channel allocation
// since 0.15.0-b3 I2S1 is favoured for classic ESP32 and moved to position 0 (channel 0) so we need to subtract 1 for correct RMT allocation // since 0.15.0-b3 I2S1 is favoured for classic ESP32 and moved to position 0 (channel 0) so we need to subtract 1 for correct RMT allocation
@ -597,7 +597,7 @@ class PolyBus {
case I_HS_P98_3: busPtr = new B_HS_P98_3(len, pins[1], pins[0]); break; case I_HS_P98_3: busPtr = new B_HS_P98_3(len, pins[1], pins[0]); break;
case I_SS_P98_3: busPtr = new B_SS_P98_3(len, pins[1], pins[0]); break; case I_SS_P98_3: busPtr = new B_SS_P98_3(len, pins[1], pins[0]); break;
} }
begin(busPtr, busType, pins, clock_kHz);
return busPtr; return busPtr;
} }

View File

@ -29,7 +29,7 @@ void shortPressAction(uint8_t b)
#ifndef WLED_DISABLE_MQTT #ifndef WLED_DISABLE_MQTT
// publish MQTT message // publish MQTT message
if (buttonPublishMqtt && WLED_MQTT_CONNECTED) { if (buttonPublishMqtt && WLED_MQTT_CONNECTED) {
char subuf[64]; char subuf[MQTT_MAX_TOPIC_LEN + 32];
sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b); sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b);
mqtt->publish(subuf, 0, false, "short"); mqtt->publish(subuf, 0, false, "short");
} }
@ -62,7 +62,7 @@ void longPressAction(uint8_t b)
#ifndef WLED_DISABLE_MQTT #ifndef WLED_DISABLE_MQTT
// publish MQTT message // publish MQTT message
if (buttonPublishMqtt && WLED_MQTT_CONNECTED) { if (buttonPublishMqtt && WLED_MQTT_CONNECTED) {
char subuf[64]; char subuf[MQTT_MAX_TOPIC_LEN + 32];
sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b); sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b);
mqtt->publish(subuf, 0, false, "long"); mqtt->publish(subuf, 0, false, "long");
} }
@ -83,19 +83,19 @@ void doublePressAction(uint8_t b)
#ifndef WLED_DISABLE_MQTT #ifndef WLED_DISABLE_MQTT
// publish MQTT message // publish MQTT message
if (buttonPublishMqtt && WLED_MQTT_CONNECTED) { if (buttonPublishMqtt && WLED_MQTT_CONNECTED) {
char subuf[64]; char subuf[MQTT_MAX_TOPIC_LEN + 32];
sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b); sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b);
mqtt->publish(subuf, 0, false, "double"); mqtt->publish(subuf, 0, false, "double");
} }
#endif #endif
} }
bool isButtonPressed(uint8_t i) bool isButtonPressed(uint8_t b)
{ {
if (btnPin[i]<0) return false; if (btnPin[b]<0) return false;
unsigned pin = btnPin[i]; unsigned pin = btnPin[b];
switch (buttonType[i]) { switch (buttonType[b]) {
case BTN_TYPE_NONE: case BTN_TYPE_NONE:
case BTN_TYPE_RESERVED: case BTN_TYPE_RESERVED:
break; 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) #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; if (touchInterruptGetLastStatus(pin)) return true;
#else #else
if (digitalPinToTouchChannel(btnPin[i]) >= 0 && touchRead(pin) <= touchThreshold) return true; if (digitalPinToTouchChannel(btnPin[b]) >= 0 && touchRead(pin) <= touchThreshold) return true;
#endif #endif
#endif #endif
break; break;
@ -151,7 +151,7 @@ void handleSwitch(uint8_t b)
#ifndef WLED_DISABLE_MQTT #ifndef WLED_DISABLE_MQTT
// publish MQTT message // publish MQTT message
if (buttonPublishMqtt && WLED_MQTT_CONNECTED) { if (buttonPublishMqtt && WLED_MQTT_CONNECTED) {
char subuf[64]; char subuf[MQTT_MAX_TOPIC_LEN + 32];
if (buttonType[b] == BTN_TYPE_PIR_SENSOR) sprintf_P(subuf, PSTR("%s/motion/%d"), mqttDeviceTopic, (int)b); if (buttonType[b] == BTN_TYPE_PIR_SENSOR) sprintf_P(subuf, PSTR("%s/motion/%d"), mqttDeviceTopic, (int)b);
else sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b); else sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b);
mqtt->publish(subuf, 0, false, !buttonPressedBefore[b] ? "off" : "on"); mqtt->publish(subuf, 0, false, !buttonPressedBefore[b] ? "off" : "on");
@ -375,6 +375,7 @@ void handleIO()
if (rlyPin>=0) { if (rlyPin>=0) {
pinMode(rlyPin, rlyOpenDrain ? OUTPUT_OPEN_DRAIN : OUTPUT); pinMode(rlyPin, rlyOpenDrain ? OUTPUT_OPEN_DRAIN : OUTPUT);
digitalWrite(rlyPin, rlyMde); digitalWrite(rlyPin, rlyMde);
delay(50); // wait for relay to switch and power to stabilize
} }
offMode = false; offMode = false;
} }

View File

@ -114,8 +114,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(strip.correctWB, hw_led["cct"]); CJSON(strip.correctWB, hw_led["cct"]);
CJSON(strip.cctFromRgb, hw_led[F("cr")]); CJSON(strip.cctFromRgb, hw_led[F("cr")]);
CJSON(cctICused, hw_led[F("ic")]); CJSON(cctICused, hw_led[F("ic")]);
CJSON(strip.cctBlending, hw_led[F("cb")]); uint8_t cctBlending = hw_led[F("cb")] | Bus::getCCTBlend();
Bus::setCCTBlend(strip.cctBlending); Bus::setCCTBlend(cctBlending);
strip.setTargetFps(hw_led["fps"]); //NOP if 0, default 42 FPS strip.setTargetFps(hw_led["fps"]); //NOP if 0, default 42 FPS
CJSON(useGlobalLedBuffer, hw_led[F("ld")]); CJSON(useGlobalLedBuffer, hw_led[F("ld")]);
@ -436,21 +436,17 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
else gammaCorrectBri = false; else gammaCorrectBri = false;
if (light_gc_col > 1.0f) gammaCorrectCol = true; if (light_gc_col > 1.0f) gammaCorrectCol = true;
else gammaCorrectCol = false; else gammaCorrectCol = false;
if (gammaCorrectVal > 1.0f && gammaCorrectVal <= 3) { if (gammaCorrectVal <= 1.0f || gammaCorrectVal > 3) {
if (gammaCorrectVal != 2.8f) NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal);
} else {
gammaCorrectVal = 1.0f; // no gamma correction gammaCorrectVal = 1.0f; // no gamma correction
gammaCorrectBri = false; gammaCorrectBri = false;
gammaCorrectCol = false; gammaCorrectCol = false;
} }
NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal); // fill look-up table
JsonObject light_tr = light["tr"]; JsonObject light_tr = light["tr"];
CJSON(fadeTransition, light_tr["mode"]);
CJSON(modeBlending, light_tr["fx"]);
int tdd = light_tr["dur"] | -1; int tdd = light_tr["dur"] | -1;
if (tdd >= 0) transitionDelay = transitionDelayDefault = tdd * 100; if (tdd >= 0) transitionDelay = transitionDelayDefault = tdd * 100;
strip.setTransition(fadeTransition ? transitionDelayDefault : 0); strip.setTransition(transitionDelayDefault);
CJSON(strip.paletteFade, light_tr["pal"]);
CJSON(randomPaletteChangeTime, light_tr[F("rpc")]); CJSON(randomPaletteChangeTime, light_tr[F("rpc")]);
CJSON(useHarmonicRandomPalette, light_tr[F("hrp")]); CJSON(useHarmonicRandomPalette, light_tr[F("hrp")]);
@ -523,6 +519,14 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
tdd = if_live[F("timeout")] | -1; tdd = if_live[F("timeout")] | -1;
if (tdd >= 0) realtimeTimeoutMs = tdd * 100; 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(arlsForceMaxBri, if_live[F("maxbri")]);
CJSON(arlsDisableGammaCorrection, if_live[F("no-gc")]); // false CJSON(arlsDisableGammaCorrection, if_live[F("no-gc")]); // false
CJSON(arlsOffset, if_live[F("offset")]); // 0 CJSON(arlsOffset, if_live[F("offset")]); // 0
@ -820,7 +824,7 @@ void serializeConfig() {
hw_led["cct"] = strip.correctWB; hw_led["cct"] = strip.correctWB;
hw_led[F("cr")] = strip.cctFromRgb; hw_led[F("cr")] = strip.cctFromRgb;
hw_led[F("ic")] = cctICused; hw_led[F("ic")] = cctICused;
hw_led[F("cb")] = strip.cctBlending; hw_led[F("cb")] = Bus::getCCTBlend();
hw_led["fps"] = strip.getTargetFps(); hw_led["fps"] = strip.getTargetFps();
hw_led[F("rgbwm")] = Bus::getGlobalAWMode(); // global auto white mode override hw_led[F("rgbwm")] = Bus::getGlobalAWMode(); // global auto white mode override
hw_led[F("ld")] = useGlobalLedBuffer; hw_led[F("ld")] = useGlobalLedBuffer;
@ -938,10 +942,7 @@ void serializeConfig() {
light_gc["val"] = gammaCorrectVal; light_gc["val"] = gammaCorrectVal;
JsonObject light_tr = light.createNestedObject("tr"); JsonObject light_tr = light.createNestedObject("tr");
light_tr["mode"] = fadeTransition;
light_tr["fx"] = modeBlending;
light_tr["dur"] = transitionDelayDefault / 100; light_tr["dur"] = transitionDelayDefault / 100;
light_tr["pal"] = strip.paletteFade;
light_tr[F("rpc")] = randomPaletteChangeTime; light_tr[F("rpc")] = randomPaletteChangeTime;
light_tr[F("hrp")] = useHarmonicRandomPalette; light_tr[F("hrp")] = useHarmonicRandomPalette;
@ -1002,6 +1003,12 @@ void serializeConfig() {
if_live_dmx[F("addr")] = DMXAddress; if_live_dmx[F("addr")] = DMXAddress;
if_live_dmx[F("dss")] = DMXSegmentSpacing; if_live_dmx[F("dss")] = DMXSegmentSpacing;
if_live_dmx["mode"] = DMXMode; 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("timeout")] = realtimeTimeoutMs / 100;
if_live[F("maxbri")] = arlsForceMaxBri; if_live[F("maxbri")] = arlsForceMaxBri;

View File

@ -5,61 +5,56 @@
*/ */
/* /*
* color blend function * color blend function, based on FastLED blend function
* the calculation for each color is: result = (A*(amountOfA) + A + B*(amountOfB) + B) / 256 with amountOfA = 255 - amountOfB
*/ */
uint32_t color_blend(uint32_t color1, uint32_t color2, uint16_t blend, bool b16) { uint32_t color_blend(uint32_t color1, uint32_t color2, uint8_t blend) {
if (blend == 0) return color1; // min / max blend checking is omitted: calls with 0 or 255 are rare, checking lowers overall performance
unsigned blendmax = b16 ? 0xFFFF : 0xFF; uint32_t rb1 = color1 & 0x00FF00FF;
if (blend == blendmax) return color2; uint32_t wg1 = (color1>>8) & 0x00FF00FF;
unsigned shift = b16 ? 16 : 8; uint32_t rb2 = color2 & 0x00FF00FF;
uint32_t wg2 = (color2>>8) & 0x00FF00FF;
uint32_t w1 = W(color1); uint32_t rb3 = ((((rb1 << 8) | rb2) + (rb2 * blend) - (rb1 * blend)) >> 8) & 0x00FF00FF;
uint32_t r1 = R(color1); uint32_t wg3 = ((((wg1 << 8) | wg2) + (wg2 * blend) - (wg1 * blend))) & 0xFF00FF00;
uint32_t g1 = G(color1); return rb3 | wg3;
uint32_t b1 = B(color1);
uint32_t w2 = W(color2);
uint32_t r2 = R(color2);
uint32_t g2 = G(color2);
uint32_t b2 = B(color2);
uint32_t w3 = ((w2 * blend) + (w1 * (blendmax - blend))) >> shift;
uint32_t r3 = ((r2 * blend) + (r1 * (blendmax - blend))) >> shift;
uint32_t g3 = ((g2 * blend) + (g1 * (blendmax - blend))) >> shift;
uint32_t b3 = ((b2 * blend) + (b1 * (blendmax - blend))) >> shift;
return RGBW32(r3, g3, b3, w3);
} }
/* /*
* color add function that preserves ratio * color add function that preserves ratio
* idea: https://github.com/Aircoookie/WLED/pull/2465 by https://github.com/Proto-molecule * original idea: https://github.com/Aircoookie/WLED/pull/2465 by https://github.com/Proto-molecule
* speed optimisations by @dedehai
*/ */
uint32_t color_add(uint32_t c1, uint32_t c2, bool fast) uint32_t color_add(uint32_t c1, uint32_t c2, bool preserveCR)
{ {
if (c1 == BLACK) return c2; if (c1 == BLACK) return c2;
if (c2 == BLACK) return c1; if (c2 == BLACK) return c1;
if (fast) { uint32_t rb = (c1 & 0x00FF00FF) + (c2 & 0x00FF00FF); // mask and add two colors at once
uint8_t r = R(c1); uint32_t wg = ((c1>>8) & 0x00FF00FF) + ((c2>>8) & 0x00FF00FF);
uint8_t g = G(c1); uint32_t r = rb >> 16; // extract single color values
uint8_t b = B(c1); uint32_t b = rb & 0xFFFF;
uint8_t w = W(c1); uint32_t w = wg >> 16;
r = qadd8(r, R(c2)); uint32_t g = wg & 0xFFFF;
g = qadd8(g, G(c2));
b = qadd8(b, B(c2)); if (preserveCR) { // preserve color ratios
w = qadd8(w, W(c2)); uint32_t max = std::max(r,g); // check for overflow note
return RGBW32(r,g,b,w); max = std::max(max,b);
max = std::max(max,w);
//unsigned max = r; // check for overflow note
//max = g > max ? g : max;
//max = b > max ? b : max;
//max = w > max ? w : max;
if (max > 255) {
uint32_t scale = (uint32_t(255)<<8) / max; // division of two 8bit (shifted) values does not work -> use bit shifts and multiplaction instead
rb = ((rb * scale) >> 8) & 0x00FF00FF; //
wg = (wg * scale) & 0xFF00FF00;
} else wg = wg << 8; //shift white and green back to correct position
return rb | wg;
} else { } else {
uint32_t r = R(c1) + R(c2); r = r > 255 ? 255 : r;
uint32_t g = G(c1) + G(c2); g = g > 255 ? 255 : g;
uint32_t b = B(c1) + B(c2); b = b > 255 ? 255 : b;
uint32_t w = W(c1) + W(c2); w = w > 255 ? 255 : w;
unsigned max = r; return RGBW32(r,g,b,w);
if (g > max) max = g;
if (b > max) max = b;
if (w > max) max = w;
if (max < 256) return RGBW32(r, g, b, w);
else return RGBW32(r * 255 / max, g * 255 / max, b * 255 / max, w * 255 / max);
} }
} }
@ -70,27 +65,53 @@ uint32_t color_add(uint32_t c1, uint32_t c2, bool fast)
uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) uint32_t color_fade(uint32_t c1, uint8_t amount, bool video)
{ {
if (c1 == BLACK || amount + video == 0) return BLACK; if (amount == 255) return c1;
if (c1 == BLACK || amount == 0) return BLACK;
uint32_t scaledcolor; // color order is: W R G B from MSB to LSB uint32_t scaledcolor; // color order is: W R G B from MSB to LSB
uint32_t r = R(c1);
uint32_t g = G(c1);
uint32_t b = B(c1);
uint32_t w = W(c1);
uint32_t scale = amount; // 32bit for faster calculation uint32_t scale = amount; // 32bit for faster calculation
if (video) { uint32_t addRemains = 0;
scaledcolor = (((r * scale) >> 8) + ((r && scale) ? 1 : 0)) << 16; if (!video) scale++; // add one for correct scaling using bitshifts
scaledcolor |= (((g * scale) >> 8) + ((g && scale) ? 1 : 0)) << 8; else { // video scaling: make sure colors do not dim to zero if they started non-zero
scaledcolor |= ((b * scale) >> 8) + ((b && scale) ? 1 : 0); addRemains = R(c1) ? 0x00010000 : 0;
scaledcolor |= (((w * scale) >> 8) + ((w && scale) ? 1 : 0)) << 24; addRemains |= G(c1) ? 0x00000100 : 0;
} else { addRemains |= B(c1) ? 0x00000001 : 0;
scaledcolor = ((r * scale) >> 8) << 16; addRemains |= W(c1) ? 0x01000000 : 0;
scaledcolor |= ((g * scale) >> 8) << 8;
scaledcolor |= (b * scale) >> 8;
scaledcolor |= ((w * scale) >> 8) << 24;
} }
uint32_t rb = (((c1 & 0x00FF00FF) * scale) >> 8) & 0x00FF00FF; // scale red and blue
uint32_t wg = (((c1 & 0xFF00FF00) >> 8) * scale) & 0xFF00FF00; // scale white and green
scaledcolor = (rb | wg) + addRemains;
return scaledcolor; return scaledcolor;
} }
// 1:1 replacement of fastled function optimized for ESP, slightly faster, more accurate and uses less flash (~ -200bytes)
uint32_t ColorFromPaletteWLED(const CRGBPalette16& pal, unsigned index, uint8_t brightness, TBlendType blendType)
{
if (blendType == LINEARBLEND_NOWRAP) {
index = (index*240) >> 8; // Blend range is affected by lo4 blend of values, remap to avoid wrapping
}
unsigned hi4 = byte(index) >> 4;
const CRGB* entry = (CRGB*)((uint8_t*)(&(pal[0])) + (hi4 * sizeof(CRGB)));
unsigned red1 = entry->r;
unsigned green1 = entry->g;
unsigned blue1 = entry->b;
if (blendType != NOBLEND) {
if (hi4 == 15) entry = &(pal[0]);
else ++entry;
unsigned f2 = ((index & 0x0F) << 4) + 1; // +1 so we scale by 256 as a max value, then result can just be shifted by 8
unsigned f1 = (257 - f2); // f2 is 1 minimum, so this is 256 max
red1 = (red1 * f1 + (unsigned)entry->r * f2) >> 8;
green1 = (green1 * f1 + (unsigned)entry->g * f2) >> 8;
blue1 = (blue1 * f1 + (unsigned)entry->b * f2) >> 8;
}
if (brightness < 255) { // note: zero checking could be done to return black but that is hardly ever used so it is omitted
uint32_t scale = brightness + 1; // adjust for rounding (bitshift)
red1 = (red1 * scale) >> 8;
green1 = (green1 * scale) >> 8;
blue1 = (blue1 * scale) >> 8;
}
return RGBW32(red1,green1,blue1,0);
}
void setRandomColor(byte* rgb) void setRandomColor(byte* rgb)
{ {
lastRandomIndex = get_random_wheel_index(lastRandomIndex); lastRandomIndex = get_random_wheel_index(lastRandomIndex);
@ -101,88 +122,88 @@ void setRandomColor(byte* rgb)
* generates a random palette based on harmonic color theory * 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 * 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 CHSV palettecolors[4]; // array of colors for the new palette
uint8_t keepcolorposition = random8(4); //color position of current random palette to keep uint8_t keepcolorposition = hw_random8(4); // color position of current random palette to keep
palettecolors[keepcolorposition] = rgb2hsv_approximate(basepalette.entries[keepcolorposition*5]); //read one of the base colors of the current palette palettecolors[keepcolorposition] = rgb2hsv(basepalette.entries[keepcolorposition*5]); // read one of the base colors of the current palette
palettecolors[keepcolorposition].hue += random8(10)-5; // +/- 5 randomness of base color palettecolors[keepcolorposition].hue += hw_random8(10)-5; // +/- 5 randomness of base color
//generate 4 saturation and brightness value numbers // generate 4 saturation and brightness value numbers
//only one saturation is allowed to be below 200 creating mostly vibrant colors // only one saturation is allowed to be below 200 creating mostly vibrant colors
//only one brightness value number is allowed below 200, creating mostly bright palettes // only one brightness value number is allowed below 200, creating mostly bright palettes
for (int i = 0; i < 3; i++) { //generate three high values for (int i = 0; i < 3; i++) { // generate three high values
palettecolors[i].saturation = random8(200,255); palettecolors[i].saturation = hw_random8(200,255);
palettecolors[i].value = random8(220,255); palettecolors[i].value = hw_random8(220,255);
} }
//allow one to be lower // allow one to be lower
palettecolors[3].saturation = random8(20,255); palettecolors[3].saturation = hw_random8(20,255);
palettecolors[3].value = random8(80,255); palettecolors[3].value = hw_random8(80,255);
//shuffle the arrays // shuffle the arrays
for (int i = 3; i > 0; i--) { for (int i = 3; i > 0; i--) {
std::swap(palettecolors[i].saturation, palettecolors[random8(i + 1)].saturation); std::swap(palettecolors[i].saturation, palettecolors[hw_random8(i + 1)].saturation);
std::swap(palettecolors[i].value, palettecolors[random8(i + 1)].value); std::swap(palettecolors[i].value, palettecolors[hw_random8(i + 1)].value);
} }
//now generate three new hues based off of the hue of the chosen current color // now generate three new hues based off of the hue of the chosen current color
uint8_t basehue = palettecolors[keepcolorposition].hue; uint8_t basehue = palettecolors[keepcolorposition].hue;
uint8_t harmonics[3]; //hues that are harmonic but still a little random uint8_t harmonics[3]; // hues that are harmonic but still a little random
uint8_t type = random8(5); //choose a harmony type uint8_t type = hw_random8(5); // choose a harmony type
switch (type) { switch (type) {
case 0: // analogous case 0: // analogous
harmonics[0] = basehue + random8(30, 50); harmonics[0] = basehue + hw_random8(30, 50);
harmonics[1] = basehue + random8(10, 30); harmonics[1] = basehue + hw_random8(10, 30);
harmonics[2] = basehue - random8(10, 30); harmonics[2] = basehue - hw_random8(10, 30);
break; break;
case 1: // triadic case 1: // triadic
harmonics[0] = basehue + 113 + random8(15); harmonics[0] = basehue + 113 + hw_random8(15);
harmonics[1] = basehue + 233 + random8(15); harmonics[1] = basehue + 233 + hw_random8(15);
harmonics[2] = basehue - 7 + random8(15); harmonics[2] = basehue - 7 + hw_random8(15);
break; break;
case 2: // split-complementary case 2: // split-complementary
harmonics[0] = basehue + 145 + random8(10); harmonics[0] = basehue + 145 + hw_random8(10);
harmonics[1] = basehue + 205 + random8(10); harmonics[1] = basehue + 205 + hw_random8(10);
harmonics[2] = basehue - 5 + random8(10); harmonics[2] = basehue - 5 + hw_random8(10);
break; break;
case 3: // square case 3: // square
harmonics[0] = basehue + 85 + random8(10); harmonics[0] = basehue + 85 + hw_random8(10);
harmonics[1] = basehue + 175 + random8(10); harmonics[1] = basehue + 175 + hw_random8(10);
harmonics[2] = basehue + 265 + random8(10); harmonics[2] = basehue + 265 + hw_random8(10);
break; break;
case 4: // tetradic case 4: // tetradic
harmonics[0] = basehue + 80 + random8(20); harmonics[0] = basehue + 80 + hw_random8(20);
harmonics[1] = basehue + 170 + random8(20); harmonics[1] = basehue + 170 + hw_random8(20);
harmonics[2] = basehue - 15 + random8(30); harmonics[2] = basehue - 15 + hw_random8(30);
break; break;
} }
if (random8() < 128) { if (hw_random8() < 128) {
//50:50 chance of shuffling hues or keep the color order // 50:50 chance of shuffling hues or keep the color order
for (int i = 2; i > 0; i--) { for (int i = 2; i > 0; i--) {
std::swap(harmonics[i], harmonics[random8(i + 1)]); std::swap(harmonics[i], harmonics[hw_random8(i + 1)]);
} }
} }
//now set the hues // now set the hues
int j = 0; int j = 0;
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
if (i==keepcolorposition) continue; //skip the base color if (i==keepcolorposition) continue; // skip the base color
palettecolors[i].hue = harmonics[j]; palettecolors[i].hue = harmonics[j];
j++; j++;
} }
bool makepastelpalette = false; bool makepastelpalette = false;
if (random8() < 25) { //~10% chance of desaturated 'pastel' colors if (hw_random8() < 25) { // ~10% chance of desaturated 'pastel' colors
makepastelpalette = true; makepastelpalette = true;
} }
//apply saturation & gamma correction // apply saturation & gamma correction
CRGB RGBpalettecolors[4]; CRGB RGBpalettecolors[4];
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
if (makepastelpalette && palettecolors[i].saturation > 180) { if (makepastelpalette && palettecolors[i].saturation > 180) {
@ -198,34 +219,72 @@ CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette)
RGBpalettecolors[3]); RGBpalettecolors[3]);
} }
CRGBPalette16 generateRandomPalette() //generate fully random palette CRGBPalette16 generateRandomPalette() // generate fully random palette
{ {
return CRGBPalette16(CHSV(random8(), random8(160, 255), random8(128, 255)), return CRGBPalette16(CHSV(hw_random8(), hw_random8(160, 255), hw_random8(128, 255)),
CHSV(random8(), random8(160, 255), random8(128, 255)), CHSV(hw_random8(), hw_random8(160, 255), hw_random8(128, 255)),
CHSV(random8(), random8(160, 255), random8(128, 255)), CHSV(hw_random8(), hw_random8(160, 255), hw_random8(128, 255)),
CHSV(random8(), random8(160, 255), random8(128, 255))); CHSV(hw_random8(), hw_random8(160, 255), hw_random8(128, 255)));
} }
void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) //hue, sat to rgb void hsv2rgb(const CHSV32& hsv, uint32_t& rgb) // convert HSV (16bit hue) to RGB (32bit with white = 0)
{ {
float h = ((float)hue)/10922.5f; // hue*6/65535 unsigned int remainder, region, p, q, t;
float s = ((float)sat)/255.0f; unsigned int h = hsv.h;
int i = int(h); unsigned int s = hsv.s;
float f = h - i; unsigned int v = hsv.v;
int p = int(255.0f * (1.0f-s)); if (s == 0) {
int q = int(255.0f * (1.0f-s*f)); rgb = v << 16 | v << 8 | v;
int t = int(255.0f * (1.0f-s*(1.0f-f))); return;
p = constrain(p, 0, 255);
q = constrain(q, 0, 255);
t = constrain(t, 0, 255);
switch (i%6) {
case 0: rgb[0]=255,rgb[1]=t, rgb[2]=p; break;
case 1: rgb[0]=q, rgb[1]=255,rgb[2]=p; break;
case 2: rgb[0]=p, rgb[1]=255,rgb[2]=t; break;
case 3: rgb[0]=p, rgb[1]=q, rgb[2]=255;break;
case 4: rgb[0]=t, rgb[1]=p, rgb[2]=255;break;
case 5: rgb[0]=255,rgb[1]=p, rgb[2]=q; break;
} }
region = h / 10923; // 65536 / 6 = 10923
remainder = (h - (region * 10923)) * 6;
p = (v * (255 - s)) >> 8;
q = (v * (255 - ((s * remainder) >> 16))) >> 8;
t = (v * (255 - ((s * (65535 - remainder)) >> 16))) >> 8;
switch (region) {
case 0:
rgb = v << 16 | t << 8 | p; break;
case 1:
rgb = q << 16 | v << 8 | p; break;
case 2:
rgb = p << 16 | v << 8 | t; break;
case 3:
rgb = p << 16 | q << 8 | v; break;
case 4:
rgb = t << 16 | p << 8 | v; break;
default:
rgb = v << 16 | p << 8 | q; break;
}
}
void rgb2hsv(const uint32_t rgb, CHSV32& hsv) // convert RGB to HSV (16bit hue), much more accurate and faster than fastled version
{
hsv.raw = 0;
int32_t r = (rgb>>16)&0xFF;
int32_t g = (rgb>>8)&0xFF;
int32_t b = rgb&0xFF;
int32_t minval, maxval, delta;
minval = min(r, g);
minval = min(minval, b);
maxval = max(r, g);
maxval = max(maxval, b);
if (maxval == 0) return; // black
hsv.v = maxval;
delta = maxval - minval;
hsv.s = (255 * delta) / maxval;
if (hsv.s == 0) return; // gray value
if (maxval == r) hsv.h = (10923 * (g - b)) / delta;
else if (maxval == g) hsv.h = 21845 + (10923 * (b - r)) / delta;
else hsv.h = 43690 + (10923 * (r - g)) / delta;
}
void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) { //hue, sat to rgb
uint32_t crgb;
hsv2rgb(CHSV32(hue, sat, 255), crgb);
rgb[0] = byte((crgb) >> 16);
rgb[1] = byte((crgb) >> 8);
rgb[2] = byte(crgb);
} }
//get RGB values from color temperature in K (https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html) //get RGB values from color temperature in K (https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html)
@ -332,7 +391,7 @@ void colorXYtoRGB(float x, float y, byte* rgb) //coordinates to rgb (https://www
rgb[2] = byte(255.0f*b); 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 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; float Y = rgb[0] * 0.283881f + rgb[1] * 0.668433f + rgb[2] * 0.047685f;
@ -343,7 +402,7 @@ void colorRGBtoXY(byte* rgb, float* xy) //rgb to coordinates (https://www.develo
#endif // WLED_DISABLE_HUESYNC #endif // WLED_DISABLE_HUESYNC
//RRGGBB / WWRRGGBB order for hex //RRGGBB / WWRRGGBB order for hex
void colorFromDecOrHexString(byte* rgb, char* in) void colorFromDecOrHexString(byte* rgb, const char* in)
{ {
if (in[0] == 0) return; if (in[0] == 0) return;
char first = in[0]; char first = in[0];
@ -452,24 +511,8 @@ uint16_t approximateKelvinFromRGB(uint32_t rgb) {
} }
} }
//gamma 2.8 lookup table used for color correction // gamma lookup table used for color correction (filled on 1st use (cfg.cpp & set.cpp))
uint8_t NeoGammaWLEDMethod::gammaT[256] = { uint8_t NeoGammaWLEDMethod::gammaT[256];
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2,
2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5,
5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10,
10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16,
17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25,
25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36,
37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50,
51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68,
69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89,
90, 92, 93, 95, 96, 98, 99,101,102,104,105,107,109,110,112,114,
115,117,119,120,122,124,126,127,129,131,133,135,137,138,140,142,
144,146,148,150,152,154,156,158,160,162,164,167,169,171,173,175,
177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213,
215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255 };
// re-calculates & fills gamma table // re-calculates & fills gamma table
void NeoGammaWLEDMethod::calcGammaTable(float gamma) void NeoGammaWLEDMethod::calcGammaTable(float gamma)

View File

@ -5,7 +5,7 @@
* Readability defines and their associated numerical values + compile-time constants * Readability defines and their associated numerical values + compile-time constants
*/ */
#define GRADIENT_PALETTE_COUNT 58 #define GRADIENT_PALETTE_COUNT 59
// You can define custom product info from build flags. // You can define custom product info from build flags.
// This is useful to allow API consumer to identify what type of WLED version // This is useful to allow API consumer to identify what type of WLED version
@ -203,6 +203,8 @@
#define USERMOD_ID_LD2410 52 //Usermod "usermod_ld2410.h" #define USERMOD_ID_LD2410 52 //Usermod "usermod_ld2410.h"
#define USERMOD_ID_POV_DISPLAY 53 //Usermod "usermod_pov_display.h" #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_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 //Access point behavior
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
@ -248,6 +250,7 @@
#define REALTIME_MODE_ARTNET 6 #define REALTIME_MODE_ARTNET 6
#define REALTIME_MODE_TPM2NET 7 #define REALTIME_MODE_TPM2NET 7
#define REALTIME_MODE_DDP 8 #define REALTIME_MODE_DDP 8
#define REALTIME_MODE_DMX 9
//realtime override modes //realtime override modes
#define REALTIME_OVERRIDE_NONE 0 #define REALTIME_OVERRIDE_NONE 0
@ -557,8 +560,25 @@
#endif #endif
#endif #endif
//#define MIN_HEAP_SIZE (8k for AsyncWebServer) //#define MIN_HEAP_SIZE
#define MIN_HEAP_SIZE 8192 #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) // Maximum size of node map (list of other WLED instances)
#ifdef ESP8266 #ifdef ESP8266

View File

@ -167,6 +167,7 @@
</div> </div>
<div style="display: flex; justify-content: center;"> <div style="display: flex; justify-content: center;">
<div id="palettes" class="palettesMain"> <div id="palettes" class="palettesMain">
<div id="distDiv" class="palTop"></div>
<div id="palTop" class="palTop"> <div id="palTop" class="palTop">
Currently in use custom palettes Currently in use custom palettes
</div> </div>
@ -204,6 +205,13 @@
var paletteName = []; // Holds the names of the palettes after load. var paletteName = []; // Holds the names of the palettes after load.
var svgSave = '<svg style="width:25px;height:25px" viewBox="0 0 24 24"><path fill=#fff d="M22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2A10,10 0 0,1 22,12M7,12L12,17V14H16V10H12V7L7,12Z"/></svg>' var svgSave = '<svg style="width:25px;height:25px" viewBox="0 0 24 24"><path fill=#fff d="M22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2A10,10 0 0,1 22,12M7,12L12,17V14H16V10H12V7L7,12Z"/></svg>'
var svgEdit = '<svg style="width:25px;height:25px" viewBox="0 0 24 24"><path fill=#fff d="M12,2C6.47,2 2,6.47 2,12C2,17.53 6.47,22 12,22C17.53,22 22,17.53 22,12C22,6.47 17.53,2 12,2M15.1,7.07C15.24,7.07 15.38,7.12 15.5,7.23L16.77,8.5C17,8.72 17,9.07 16.77,9.28L15.77,10.28L13.72,8.23L14.72,7.23C14.82,7.12 14.96,7.07 15.1,7.07M13.13,8.81L15.19,10.87L9.13,16.93H7.07V14.87L13.13,8.81Z"/></svg>' var svgEdit = '<svg style="width:25px;height:25px" viewBox="0 0 24 24"><path fill=#fff d="M12,2C6.47,2 2,6.47 2,12C2,17.53 6.47,22 12,22C17.53,22 22,17.53 22,12C22,6.47 17.53,2 12,2M15.1,7.07C15.24,7.07 15.38,7.12 15.5,7.23L16.77,8.5C17,8.72 17,9.07 16.77,9.28L15.77,10.28L13.72,8.23L14.72,7.23C14.82,7.12 14.96,7.07 15.1,7.07M13.13,8.81L15.19,10.87L9.13,16.93H7.07V14.87L13.13,8.81Z"/></svg>'
var svgDist = '<svg style="width:25px;height:25px" viewBox="0 0 24 24"><path fill=#fff d="M4 22H2V2H4V22M22 2H20V22H22V2M13.5 7H10.5V17H13.5V7Z"/></svg>'
var svgTrash = '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="30px" height="30px"><path style="fill:#880000; stroke: #888888; stroke-width: -2px;stroke-dasharray: 0.1, 8;" d="M9,3V4H4V6H5V19A2,2 0 0,0 7,21H17A2,2 0 0,0 19,19V6H20V4H15V3H9M7,6H17V19H7V6M9,8V17H11V8H9M13,8V17H15V8H13Z"/></svg>'
const distDiv = gId("distDiv");
distDiv.addEventListener('click', distribute);
distDiv.setAttribute('title', 'Distribute colors equally');
distDiv.innerHTML = svgDist;
function recOf() { function recOf() {
rect = gradientBox.getBoundingClientRect(); rect = gradientBox.getBoundingClientRect();
@ -433,7 +441,7 @@
renderY = e.srcElement.getBoundingClientRect().y + 13; renderY = e.srcElement.getBoundingClientRect().y + 13;
trash.id = "trash"; trash.id = "trash";
trash.innerHTML = '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="30px" height="30px"><path style="fill:#880000; stroke: #888888; stroke-width: -2px;stroke-dasharray: 0.1, 8;" d="M9,3V4H4V6H5V19A2,2 0 0,0 7,21H17A2,2 0 0,0 19,19V6H20V4H15V3H9M7,6H17V19H7V6M9,8V17H11V8H9M13,8V17H15V8H13Z"/></svg>'; trash.innerHTML = svgTrash;
trash.style.position = "absolute"; trash.style.position = "absolute";
trash.style.left = (renderX) + "px"; trash.style.left = (renderX) + "px";
trash.style.top = (renderY) + "px"; trash.style.top = (renderY) + "px";
@ -712,9 +720,27 @@
} }
} }
function distribute() {
let colorMarkers = [...gradientBox.querySelectorAll('.color-marker')];
colorMarkers.sort((a, b) => a.getAttribute('data-truepos') - b.getAttribute('data-truepos'));
colorMarkers = colorMarkers.slice(1, -1);
const spacing = Math.round(256 / (colorMarkers.length + 1));
colorMarkers.forEach((e, i) => {
const markerId = e.id.match(/\d+/)[0];
const trueCol = e.getAttribute("data-truecol");
gradientBox.removeChild(e);
gradientBox.removeChild(gId(`colorPicker${markerId}`));
gradientBox.removeChild(gId(`colorPickerMarker${markerId}`));
gradientBox.removeChild(gId(`deleteMarker${markerId}`));
addC(spacing * (i + 1), trueCol);
});
}
function rgbToHex(r, g, b) { function rgbToHex(r, g, b) {
const hex = ((r << 16) | (g << 8) | b).toString(16); const hex = ((r << 16) | (g << 8) | b).toString(16);
return "#" + "0".repeat(6 - hex.length) + hex; return "#" + "0".repeat(6 - hex.length) + hex;
} }
</script> </script>
</html> </html>

View File

@ -97,6 +97,7 @@ button {
.labels { .labels {
margin: 0; margin: 0;
padding: 8px 0 2px 0; padding: 8px 0 2px 0;
font-size: 19px;
} }
#namelabel { #namelabel {
@ -890,12 +891,12 @@ a.btn {
line-height: 28px; line-height: 28px;
} }
/* Quick color select Black button (has white border) */ /* Quick color select Black and White button (has white/black border, depending on the theme) */
.qcsb { .qcsb, .qcsw {
width: 26px; width: 26px;
height: 26px; height: 26px;
line-height: 26px; line-height: 26px;
border: 1px solid #fff; border: 1px solid var(--c-f);
} }
/* Hex color input wrapper div */ /* Hex color input wrapper div */
@ -1299,6 +1300,14 @@ TD .checkmark, TD .radiomark {
width: 100%; width: 100%;
} }
#segutil {
margin-bottom: 12px;
}
#segcont > div:first-child, #fxFind {
margin-top: 4px;
}
/* Simplify segments */ /* Simplify segments */
.simplified #segcont .lstI { .simplified #segcont .lstI {
margin-top: 4px; margin-top: 4px;
@ -1438,6 +1447,11 @@ dialog {
position: relative; position: relative;
} }
.presin {
width: 100%;
box-sizing: border-box;
}
.btn-s, .btn-s,
.btn-n { .btn-n {
border: 1px solid var(--c-2); border: 1px solid var(--c-2);

View File

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

View File

@ -677,8 +677,10 @@ function parseInfo(i) {
isM = mw>0 && mh>0; isM = mw>0 && mh>0;
if (!isM) { if (!isM) {
gId("filter2D").classList.add('hide'); gId("filter2D").classList.add('hide');
gId('bs').querySelectorAll('option[data-type="2D"]').forEach((o,i)=>{o.style.display='none';});
} else { } else {
gId("filter2D").classList.remove('hide'); gId("filter2D").classList.remove('hide');
gId('bs').querySelectorAll('option[data-type="2D"]').forEach((o,i)=>{o.style.display='';});
} }
// if (i.noaudio) { // if (i.noaudio) {
// gId("filterVol").classList.add("hide"); // gId("filterVol").classList.add("hide");
@ -1437,6 +1439,9 @@ function readState(s,command=false)
tr = s.transition; tr = s.transition;
gId('tt').value = tr/10; 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); populateSegments(s);
var selc=0; var selc=0;
@ -1698,6 +1703,7 @@ function requestJson(command=null)
var tn = parseInt(t.value*10); var tn = parseInt(t.value*10);
if (tn != tr) command.transition = tn; if (tn != tr) command.transition = tn;
} }
//command.bs = parseInt(gId('bs').value);
req = JSON.stringify(command); req = JSON.stringify(command);
if (req.length > 1340) useWs = false; // do not send very long requests over websocket 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 if (req.length > 500 && lastinfo && lastinfo.arch == "esp8266") useWs = false; // esp8266 can only handle 500 bytes
@ -2846,12 +2852,16 @@ function search(field, listId = null) {
// filter list items but leave (Default & Solid) always visible // filter list items but leave (Default & Solid) always visible
const listItems = gId(listId).querySelectorAll('.lstI'); const listItems = gId(listId).querySelectorAll('.lstI');
listItems.forEach((listItem,i)=>{ listItems.forEach((listItem, i) => {
if (listId!=='pcont' && i===0) return; if (listId !== 'pcont' && i === 0) return;
const listItemName = listItem.querySelector('.lstIname').innerText.toUpperCase(); const listItemName = listItem.querySelector('.lstIname').innerText.toUpperCase();
const searchIndex = listItemName.indexOf(field.value.toUpperCase()); const searchIndex = listItemName.indexOf(field.value.toUpperCase());
listItem.style.display = (searchIndex < 0) ? 'none' : ''; if (searchIndex < 0) {
listItem.dataset.searchIndex = Number.MAX_SAFE_INTEGER;
} else {
listItem.dataset.searchIndex = searchIndex; listItem.dataset.searchIndex = searchIndex;
}
listItem.style.display = (searchIndex < 0) && !listItem.classList.contains("selected") ? 'none' : '';
}); });
// sort list items by search index and name // sort list items by search index and name
@ -2920,11 +2930,11 @@ function filterFx() {
inputField.value = ''; inputField.value = '';
inputField.focus(); inputField.focus();
clean(inputField.nextElementSibling); clean(inputField.nextElementSibling);
gId("fxlist").querySelectorAll('.lstI').forEach((listItem,i) => { gId("fxlist").querySelectorAll('.lstI').forEach((listItem, i) => {
const listItemName = listItem.querySelector('.lstIname').innerText; const listItemName = listItem.querySelector('.lstIname').innerText;
let hide = false; let hide = false;
gId("filters").querySelectorAll("input[type=checkbox]").forEach((e) => { if (e.checked && !listItemName.includes(e.dataset.flt)) hide = i>0 /*true*/; }); gId("filters").querySelectorAll("input[type=checkbox]").forEach((e) => { if (e.checked && !listItemName.includes(e.dataset.flt)) hide = i > 0 /*true*/; });
listItem.style.display = hide ? 'none' : ''; listItem.style.display = hide && !listItem.classList.contains("selected") ? 'none' : '';
}); });
} }

View File

@ -54,8 +54,8 @@ Orientation: <select name="P${i}V" oninput="UI()">
</select><br> </select><br>
Serpentine: <input type="checkbox" name="P${i}S" oninput="UI()"><br> Serpentine: <input type="checkbox" name="P${i}S" oninput="UI()"><br>
Dimensions (WxH): <input name="P${i}W" type="number" min="1" max="255" value="${pw}" oninput="UI()"> x <input name="P${i}H" type="number" min="1" max="255" value="${ph}" oninput="UI()"><br> Dimensions (WxH): <input name="P${i}W" type="number" min="1" max="255" value="${pw}" oninput="UI()"> x <input name="P${i}H" type="number" min="1" max="255" value="${ph}" oninput="UI()"><br>
Offset X:<input name="P${i}X" type="number" min="0" max="255" value="0" oninput="UI()"> Offset X: <input name="P${i}X" type="number" min="0" max="255" value="0" oninput="UI()">
Y:<input name="P${i}Y" type="number" min="0" max="255" value="0" oninput="UI()"><br><i>(offset from top-left corner in # LEDs)</i> Y: <input name="P${i}Y" type="number" min="0" max="255" value="0" oninput="UI()"><br><i>(offset from top-left corner in # LEDs)</i>
</div>`; </div>`;
p.insertAdjacentHTML("beforeend", b); p.insertAdjacentHTML("beforeend", b);
} }

View File

@ -6,7 +6,7 @@
<title>LED Settings</title> <title>LED Settings</title>
<script src="common.js" async type="text/javascript"></script> <script src="common.js" async type="text/javascript"></script>
<script> <script>
var laprev=55,maxB=1,maxD=1,maxA=1,maxV=0,maxM=4000,maxPB=2048,maxL=1664,maxCO=5,maxLbquot=0; //maximum bytes for LED allocation: 4kB for 8266, 32kB for 32 var maxB=1,maxD=1,maxA=1,maxV=0,maxM=4000,maxPB=2048,maxL=1664,maxCO=5; //maximum bytes for LED allocation: 4kB for 8266, 32kB for 32
var oMaxB=1; var oMaxB=1;
var customStarts=false,startsDirty=[]; var customStarts=false,startsDirty=[];
function off(n) { gN(n).value = -1;} function off(n) { gN(n).value = -1;}
@ -42,15 +42,14 @@
if (loc) d.Sf.action = getURL('/settings/leds'); if (loc) d.Sf.action = getURL('/settings/leds');
} }
function bLimits(b,v,p,m,l,o=5,d=2,a=6) { function bLimits(b,v,p,m,l,o=5,d=2,a=6) {
// maxB - max buses (can be changed if using ESP32 parallel I2S) oMaxB = maxB = b; // maxB - max buses (can be changed if using ESP32 parallel I2S)
// maxD - max digital channels (can be changed if using ESP32 parallel I2S) maxD = d; // maxD - max digital channels (can be changed if using ESP32 parallel I2S)
// maxA - max analog channels maxA = a; // maxA - max analog channels
// maxV - min virtual buses maxV = v; // maxV - min virtual buses
// maxPB - max LEDs per bus maxPB = p; // maxPB - max LEDs per bus
// maxM - max LED memory maxM = m; // maxM - max LED memory
// maxL - max LEDs (will serve to determine ESP >1664 == ESP32) maxL = l; // maxL - max LEDs (will serve to determine ESP >1664 == ESP32)
// maxCO - max Color Order mappings maxCO = o; // maxCO - max Color Order mappings
oMaxB = maxB = b; maxD = d, maxA = a, maxV = v; maxM = m; maxPB = p; maxL = l; maxCO = o;
} }
function pinsOK() { function pinsOK() {
var ok = true; var ok = true;
@ -380,6 +379,11 @@
gId('psu').innerHTML = s; gId('psu').innerHTML = s;
gId('psu2').innerHTML = s2; gId('psu2').innerHTML = s2;
gId("json").style.display = d.Sf.IT.value==8 ? "" : "none"; gId("json").style.display = d.Sf.IT.value==8 ? "" : "none";
// show/hide FPS warning messages
gId('fpsNone').style.display = (d.Sf.FR.value == 0) ? 'block':'none';
gId('fpsWarn').style.display = (d.Sf.FR.value == 0) || (d.Sf.FR.value >= 80) ? 'block':'none';
gId('fpsHigh').style.display = (d.Sf.FR.value >= 80) ? 'block':'none';
} }
function lastEnd(i) { function lastEnd(i) {
if (i-- < 1) return 0; if (i-- < 1) return 0;
@ -472,6 +476,8 @@ mA/LED: <select name="LAsel${s}" onchange="enLA(this,'${s}');UI();">
if (i >= maxB || twopinB >= 1) disable(sel,'option[data-type="2P"]'); // NOTE: see isD2P() if (i >= maxB || twopinB >= 1) disable(sel,'option[data-type="2P"]'); // NOTE: see isD2P()
disable(sel,`option[data-type^="${'A'.repeat(maxA-analogB+1)}"]`); // NOTE: see isPWM() disable(sel,`option[data-type^="${'A'.repeat(maxA-analogB+1)}"]`); // NOTE: see isPWM()
sel.selectedIndex = sel.querySelector('option:not(:disabled)').index; sel.selectedIndex = sel.querySelector('option:not(:disabled)').index;
// initialize current limiter
enLA(d.Sf["LAsel"+s],s);
} }
if (n==-1) { if (n==-1) {
o[--i].remove();--i; o[--i].remove();--i;
@ -755,7 +761,7 @@ Swap: <select id="xw${s}" name="XW${s}">
Enable automatic brightness limiter: <input type="checkbox" name="ABL" onchange="enABL()"><br> Enable automatic brightness limiter: <input type="checkbox" name="ABL" onchange="enABL()"><br>
<div id="abl"> <div id="abl">
<i>Automatically limits brightness to stay close to the limit.<br> <i>Automatically limits brightness to stay close to the limit.<br>
Keep at &lt;1A if poweing LEDs directly from the ESP 5V pin!<br> Keep at &lt;1A if powering LEDs directly from the ESP 5V pin!<br>
If using multiple outputs it is recommended to use per-output limiter.<br> If using multiple outputs it is recommended to use per-output limiter.<br>
Analog (PWM) and virtual LEDs cannot use automatic brightness limiter.<br></i> Analog (PWM) and virtual LEDs cannot use automatic brightness limiter.<br></i>
<div id="psuMA">Maximum PSU Current: <input name="MA" type="number" class="xl" min="250" max="65000" oninput="UI()" required> mA<br></div> <div id="psuMA">Maximum PSU Current: <input name="MA" type="number" class="xl" min="250" max="65000" oninput="UI()" required> mA<br></div>
@ -826,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> 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> % Brightness factor: <input name="BF" type="number" class="m" min="1" max="255" required> %
<h3>Transitions</h3> <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> 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>
<i>Random Cycle</i> Palette Time: <input name="TP" type="number" class="m" min="1" max="255"> s<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> Use harmonic <i>Random Cycle</i> Palette: <input type="checkbox" name="TH"><br>
<h3>Timed light</h3> <h3>Timed light</h3>
@ -870,7 +871,10 @@ Swap: <select id="xw${s}" name="XW${s}">
<option value="2">Linear (never wrap)</option> <option value="2">Linear (never wrap)</option>
<option value="3">None (not recommended)</option> <option value="3">None (not recommended)</option>
</select><br> </select><br>
Target refresh rate: <input type="number" class="s" min="1" max="120" name="FR" required> FPS Target refresh rate: <input type="number" class="s" min="0" max="250" name="FR" oninput="UI()" required> FPS
<div id="fpsNone" class="warn" style="display: none;">&#9888; Unlimited FPS Mode is experimental &#9888;<br></div>
<div id="fpsHigh" class="warn" style="display: none;">&#9888; High FPS Mode is experimental.<br></div>
<div id="fpsWarn" class="warn" style="display: none;">Please <a class="lnk" href="sec#backup">backup</a> WLED configuration and presets first!<br></div>
<hr class="sml"> <hr class="sml">
<div id="cfg">Config template: <input type="file" name="data2" accept=".json"><button type="button" class="sml" onclick="loadCfg(d.Sf.data2)">Apply</button><br></div> <div id="cfg">Config template: <input type="file" name="data2" accept=".json"><button type="button" class="sml" onclick="loadCfg(d.Sf.data2)">Apply</button><br></div>
<hr> <hr>

View File

@ -57,11 +57,11 @@
<h3>Software Update</h3> <h3>Software Update</h3>
<button type="button" onclick="U()">Manual OTA Update</button><br> <button type="button" onclick="U()">Manual OTA Update</button><br>
Enable ArduinoOTA: <input type="checkbox" name="AO"> Enable ArduinoOTA: <input type="checkbox" name="AO">
<hr> <hr id="backup">
<h3>Backup & Restore</h3> <h3>Backup & Restore</h3>
<div class="warn">&#9888; Restoring presets/configuration will OVERWRITE your current presets/configuration.<br> <div class="warn">&#9888; Restoring presets/configuration will OVERWRITE your current presets/configuration.<br>
Incorrect upload or configuration may require a factory reset or re-flashing of your ESP.</div> Incorrect upload or configuration may require a factory reset or re-flashing of your ESP.<br>
For security reasons, passwords are not backed up. For security reasons, passwords are not backed up.</div>
<a class="btn lnk" id="bckcfg" href="/presets.json" download="presets">Backup presets</a><br> <a class="btn lnk" id="bckcfg" href="/presets.json" download="presets">Backup presets</a><br>
<div>Restore presets<br><input type="file" name="data" accept=".json"> <button type="button" onclick="uploadFile(d.Sf.data,'/presets.json');">Upload</button><br></div><br> <div>Restore presets<br><input type="file" name="data" accept=".json"> <button type="button" onclick="uploadFile(d.Sf.data,'/presets.json');">Upload</button><br></div><br>
<a class="btn lnk" id="bckpresets" href="/cfg.json" download="cfg">Backup configuration</a><br> <a class="btn lnk" id="bckpresets" href="/cfg.json" download="cfg">Backup configuration</a><br>

View File

@ -40,6 +40,8 @@
function getURL(path) { function getURL(path) {
return (loc ? locproto + "//" + locip : "") + path; return (loc ? locproto + "//" + locip : "") + path;
} }
function hideDMXInput(){gId("dmxInput").style.display="none";}
function hideNoDMXInput(){gId("dmxInputOff").style.display="none";}
</script> </script>
<style>@import url("style.css");</style> <style>@import url("style.css");</style>
</head> </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> Force max brightness: <input type="checkbox" name="FB"><br>
Disable realtime gamma correction: <input type="checkbox" name="RG"><br> Disable realtime gamma correction: <input type="checkbox" name="RG"><br>
Realtime LED offset: <input name="WO" type="number" min="-255" max="255" required> 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"> <hr class="sml">
<h3>Alexa Voice Assistant</h3> <h3>Alexa Voice Assistant</h3>
<div id="NoAlexa" class="hide"> <div id="NoAlexa" class="hide">

View File

@ -17,7 +17,7 @@
<h2>WLED Software Update</h2> <h2>WLED Software Update</h2>
<form method='POST' action='./update' id='uf' enctype='multipart/form-data' onsubmit="U()"> <form method='POST' action='./update' id='uf' enctype='multipart/form-data' onsubmit="U()">
Installed version: <span class="sip">##VERSION##</span><br> Installed version: <span class="sip">##VERSION##</span><br>
Download the latest binary:&nbsp;<a href="https://github.com/Aircoookie/WLED/releases" target="_blank" Download the latest binary: <a href="https://github.com/Aircoookie/WLED/releases" target="_blank"
style="vertical-align: text-bottom; display: inline-flex;"> style="vertical-align: text-bottom; display: inline-flex;">
<img src="https://img.shields.io/github/release/Aircoookie/WLED.svg?style=flat-square"></a><br> <img src="https://img.shields.io/github/release/Aircoookie/WLED.svg?style=flat-square"></a><br>
<input type='file' name='update' required><br> <!--should have accept='.bin', but it prevents file upload from android app--> <input type='file' name='update' required><br> <!--should have accept='.bin', but it prevents file upload from android app-->

280
wled00/dmx_input.cpp Normal file
View File

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

73
wled00/dmx_input.h Normal file
View File

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

View File

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

View File

@ -39,6 +39,7 @@ void handleDDPPacket(e131_packet_t* p) {
realtimeLock(realtimeTimeoutMs, REALTIME_MODE_DDP); realtimeLock(realtimeTimeoutMs, REALTIME_MODE_DDP);
if (!realtimeOverride || (realtimeMode && useMainSegmentOnly)) { if (!realtimeOverride || (realtimeMode && useMainSegmentOnly)) {
if (useMainSegmentOnly) strip.getMainSegment().beginDraw();
for (unsigned i = start; i < stop; i++, c += ddpChannelsPerLed) { for (unsigned i = start; i < stop; i++, c += ddpChannelsPerLed) {
setRealtimePixel(i, data[c], data[c+1], data[c+2], ddpChannelsPerLed >3 ? data[c+3] : 0); setRealtimePixel(i, data[c], data[c+1], data[c+2], ddpChannelsPerLed >3 ? data[c+3] : 0);
} }
@ -115,6 +116,11 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
// update status info // update status info
realtimeIP = clientIP; 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; byte wChannel = 0;
unsigned totalLen = strip.getLengthTotal(); unsigned totalLen = strip.getLengthTotal();
unsigned availDMXLen = 0; unsigned availDMXLen = 0;
@ -129,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 // 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--; dataOffset--;
} }
@ -147,6 +153,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return; if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return;
wChannel = (availDMXLen > 3) ? e131_data[dataOffset+3] : 0; wChannel = (availDMXLen > 3) ? e131_data[dataOffset+3] : 0;
if (useMainSegmentOnly) strip.getMainSegment().beginDraw();
for (unsigned i = 0; i < totalLen; i++) for (unsigned i = 0; i < totalLen; i++)
setRealtimePixel(i, e131_data[dataOffset+0], e131_data[dataOffset+1], e131_data[dataOffset+2], wChannel); setRealtimePixel(i, e131_data[dataOffset+0], e131_data[dataOffset+1], e131_data[dataOffset+2], wChannel);
break; break;
@ -164,6 +171,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
strip.setBrightness(bri, true); strip.setBrightness(bri, true);
} }
if (useMainSegmentOnly) strip.getMainSegment().beginDraw();
for (unsigned i = 0; i < totalLen; i++) for (unsigned i = 0; i < totalLen; i++)
setRealtimePixel(i, e131_data[dataOffset+1], e131_data[dataOffset+2], e131_data[dataOffset+3], wChannel); setRealtimePixel(i, e131_data[dataOffset+1], e131_data[dataOffset+2], e131_data[dataOffset+3], wChannel);
break; break;
@ -208,7 +216,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
else else
dataOffset = DMXAddress; dataOffset = DMXAddress;
// Modify address for Art-Net data // Modify address for Art-Net data
if (protocol == P_ARTNET && dataOffset > 0) if (mde == REALTIME_MODE_ARTNET && dataOffset > 0)
dataOffset--; dataOffset--;
// Skip out of universe addresses // Skip out of universe addresses
if (dataOffset > dmxChannels - dmxEffectChannels + 1) if (dataOffset > dmxChannels - dmxEffectChannels + 1)
@ -282,7 +290,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
} }
} else { } else {
// All subsequent universes start at the first channel. // 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; const unsigned dimmerOffset = (DMXMode == DMX_MODE_MULTIPLE_DRGB) ? 1 : 0;
unsigned ledsInFirstUniverse = (((MAX_CHANNELS_PER_UNIVERSE - DMXAddress) + dmxLenOffset) - dimmerOffset) / dmxChannelsPerLed; unsigned ledsInFirstUniverse = (((MAX_CHANNELS_PER_UNIVERSE - DMXAddress) + dmxLenOffset) - dimmerOffset) / dmxChannelsPerLed;
previousLeds = ledsInFirstUniverse + (previousUniverses - 1) * ledsPerUniverse; previousLeds = ledsInFirstUniverse + (previousUniverses - 1) * ledsPerUniverse;
@ -308,6 +316,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
} }
} }
if (useMainSegmentOnly) strip.getMainSegment().beginDraw();
if (!is4Chan) { if (!is4Chan) {
for (unsigned i = previousLeds; i < ledsTotal; i++) { for (unsigned i = previousLeds; i < ledsTotal; i++) {
setRealtimePixel(i, e131_data[dmxOffset], e131_data[dmxOffset+1], e131_data[dmxOffset+2], 0); setRealtimePixel(i, e131_data[dmxOffset], e131_data[dmxOffset+1], e131_data[dmxOffset+2], 0);

View File

@ -66,6 +66,89 @@ typedef struct WiFiConfig {
} wifi_config; } wifi_config;
//colors.cpp //colors.cpp
#define ColorFromPalette ColorFromPaletteWLED // override fastled version
// CRGBW can be used to manipulate 32bit colors faster. However: if it is passed to functions, it adds overhead compared to a uint32_t color
// use with caution and pay attention to flash size. Usually converting a uint32_t to CRGBW to extract r, g, b, w values is slower than using bitshifts
// it can be useful to avoid back and forth conversions between uint32_t and fastled CRGB
struct CRGBW {
union {
uint32_t color32; // Access as a 32-bit value (0xWWRRGGBB)
struct {
uint8_t b;
uint8_t g;
uint8_t r;
uint8_t w;
};
uint8_t raw[4]; // Access as an array in the order B, G, R, W
};
// Default constructor
inline CRGBW() __attribute__((always_inline)) = default;
// Constructor from a 32-bit color (0xWWRRGGBB)
constexpr CRGBW(uint32_t color) __attribute__((always_inline)) : color32(color) {}
// Constructor with r, g, b, w values
constexpr CRGBW(uint8_t red, uint8_t green, uint8_t blue, uint8_t white = 0) __attribute__((always_inline)) : b(blue), g(green), r(red), w(white) {}
// Constructor from CRGB
constexpr CRGBW(CRGB rgb) __attribute__((always_inline)) : b(rgb.b), g(rgb.g), r(rgb.r), w(0) {}
// Access as an array
inline const uint8_t& operator[] (uint8_t x) const __attribute__((always_inline)) { return raw[x]; }
// Assignment from 32-bit color
inline CRGBW& operator=(uint32_t color) __attribute__((always_inline)) { color32 = color; return *this; }
// Assignment from r, g, b, w
inline CRGBW& operator=(const CRGB& rgb) __attribute__((always_inline)) { b = rgb.b; g = rgb.g; r = rgb.r; w = 0; return *this; }
// Conversion operator to uint32_t
inline operator uint32_t() const __attribute__((always_inline)) {
return color32;
}
/*
// Conversion operator to CRGB
inline operator CRGB() const __attribute__((always_inline)) {
return CRGB(r, g, b);
}
CRGBW& scale32 (uint8_t scaledown) // 32bit math
{
if (color32 == 0) return *this; // 2 extra instructions, worth it if called a lot on black (which probably is true) adding check if scaledown is zero adds much more overhead as its 8bit
uint32_t scale = scaledown + 1;
uint32_t rb = (((color32 & 0x00FF00FF) * scale) >> 8) & 0x00FF00FF; // scale red and blue
uint32_t wg = (((color32 & 0xFF00FF00) >> 8) * scale) & 0xFF00FF00; // scale white and green
color32 = rb | wg;
return *this;
}*/
};
struct CHSV32 { // 32bit HSV color with 16bit hue for more accurate conversions
union {
struct {
uint16_t h; // hue
uint8_t s; // saturation
uint8_t v; // value
};
uint32_t raw; // 32bit access
};
inline CHSV32() __attribute__((always_inline)) = default; // default constructor
/// Allow construction from hue, saturation, and value
/// @param ih input hue
/// @param is input saturation
/// @param iv input value
inline CHSV32(uint16_t ih, uint8_t is, uint8_t iv) __attribute__((always_inline)) // constructor from 16bit h, s, v
: h(ih), s(is), v(iv) {}
inline CHSV32(uint8_t ih, uint8_t is, uint8_t iv) __attribute__((always_inline)) // constructor from 8bit h, s, v
: h((uint16_t)ih << 8), s(is), v(iv) {}
inline CHSV32(const CHSV& chsv) __attribute__((always_inline)) // constructor from CHSV
: h((uint16_t)chsv.h << 8), s(chsv.s), v(chsv.v) {}
inline operator CHSV() const { return CHSV((uint8_t)(h >> 8), s, v); } // typecast to CHSV
};
// similar to NeoPixelBus NeoGammaTableMethod but allows dynamic changes (superseded by NPB::NeoGammaDynamicTableMethod) // similar to NeoPixelBus NeoGammaTableMethod but allows dynamic changes (superseded by NPB::NeoGammaDynamicTableMethod)
class NeoGammaWLEDMethod { class NeoGammaWLEDMethod {
public: public:
@ -78,43 +161,53 @@ class NeoGammaWLEDMethod {
}; };
#define gamma32(c) NeoGammaWLEDMethod::Correct32(c) #define gamma32(c) NeoGammaWLEDMethod::Correct32(c)
#define gamma8(c) NeoGammaWLEDMethod::rawGamma8(c) #define gamma8(c) NeoGammaWLEDMethod::rawGamma8(c)
[[gnu::hot]] uint32_t color_blend(uint32_t,uint32_t,uint16_t,bool b16=false); [[gnu::hot, gnu::pure]] uint32_t color_blend(uint32_t c1, uint32_t c2 , uint8_t blend);
[[gnu::hot]] uint32_t color_add(uint32_t,uint32_t, bool fast=false); 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_fade(uint32_t c1, uint8_t amount, bool video=false); [[gnu::hot, gnu::pure]] uint32_t color_add(uint32_t, uint32_t, bool preserveCR = false);
CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette); [[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(); 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]))); } 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 colorHStoRGB(uint16_t hue, byte sat, byte* rgb); //hue, sat to rgb void hsv2rgb(const CHSV32& hsv, uint32_t& rgb);
void colorHStoRGB(uint16_t hue, byte sat, byte* rgb);
void rgb2hsv(const uint32_t rgb, CHSV32& hsv);
inline CHSV rgb2hsv(const CRGB c) { CHSV32 hsv; rgb2hsv((uint32_t((byte(c.r) << 16) | (byte(c.g) << 8) | (byte(c.b)))), hsv); return CHSV(hsv); } // CRGB to hsv
void colorKtoRGB(uint16_t kelvin, byte* rgb); void colorKtoRGB(uint16_t kelvin, byte* rgb);
void colorCTtoRGB(uint16_t mired, byte* rgb); //white spectrum to 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 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 colorRGBtoXY(const byte* rgb, float* xy); // only defined if huesync disabled TODO
void colorFromDecOrHexString(byte* rgb, char* in); void colorFromDecOrHexString(byte* rgb, const char* in);
bool colorFromHexString(byte* rgb, const char* in); bool colorFromHexString(byte* rgb, const char* in);
uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb); uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
uint16_t approximateKelvinFromRGB(uint32_t rgb); uint16_t approximateKelvinFromRGB(uint32_t rgb);
void setRandomColor(byte* rgb); void setRandomColor(byte* rgb);
//dmx.cpp //dmx_output.cpp
void initDMX(); void initDMXOutput();
void handleDMX(); void handleDMXOutput();
//dmx_input.cpp
void initDMXInput();
void handleDMXInput();
//e131.cpp //e131.cpp
void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol); 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 handleArtnetPollReply(IPAddress ipAddress);
void prepareArtnetPollReply(ArtPollReply* reply); void prepareArtnetPollReply(ArtPollReply* reply);
void sendArtnetPollReply(ArtPollReply* reply, IPAddress ipAddress, uint16_t portAddress); void sendArtnetPollReply(ArtPollReply* reply, IPAddress ipAddress, uint16_t portAddress);
//file.cpp //file.cpp
bool handleFileRead(AsyncWebServerRequest*, String path); bool handleFileRead(AsyncWebServerRequest*, String path);
bool writeObjectToFileUsingId(const char* file, uint16_t id, JsonDocument* content); bool writeObjectToFileUsingId(const char* file, uint16_t id, const JsonDocument* content);
bool writeObjectToFile(const char* file, const char* key, JsonDocument* content); bool writeObjectToFile(const char* file, const char* key, const JsonDocument* content);
bool readObjectFromFileUsingId(const char* file, uint16_t id, JsonDocument* dest); bool readObjectFromFileUsingId(const char* file, uint16_t id, JsonDocument* dest);
bool readObjectFromFile(const char* file, const char* key, JsonDocument* dest); bool readObjectFromFile(const char* file, const char* key, JsonDocument* dest);
void updateFSInfo(); void updateFSInfo();
void closeFile(); void closeFile();
inline bool writeObjectToFileUsingId(const String &file, uint16_t id, JsonDocument* content) { return writeObjectToFileUsingId(file.c_str(), id, 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, JsonDocument* content) { return writeObjectToFile(file.c_str(), key, 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 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); }; inline bool readObjectFromFile(const String &file, const char* key, JsonDocument* dest) { return readObjectFromFile(file.c_str(), key, dest); };
@ -126,6 +219,19 @@ void onHueConnect(void* arg, AsyncClient* client);
void sendHuePoll(); void sendHuePoll();
void onHueData(void* arg, AsyncClient* client, void *data, size_t len); 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 //improv.cpp
enum ImprovRPCType { enum ImprovRPCType {
Command_Wifi = 0x01, Command_Wifi = 0x01,
@ -155,11 +261,11 @@ void handleIR();
bool deserializeSegment(JsonObject elem, byte it, byte presetId = 0); bool deserializeSegment(JsonObject elem, byte it, byte presetId = 0);
bool deserializeState(JsonObject root, byte callMode = CALL_MODE_DIRECT_CHANGE, 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 serializeState(JsonObject root, bool forPreset = false, bool includeBri = true, bool segmentBounds = true, bool selectedSegmentsOnly = false);
void serializeInfo(JsonObject root); void serializeInfo(JsonObject root);
void serializeModeNames(JsonArray root); void serializeModeNames(JsonArray arr);
void serializeModeData(JsonArray root); void serializeModeData(JsonArray fxdata);
void serveJson(AsyncWebServerRequest* request); void serveJson(AsyncWebServerRequest* request);
#ifdef WLED_ENABLE_JSONLIVE #ifdef WLED_ENABLE_JSONLIVE
bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient = 0); bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient = 0);
@ -230,7 +336,8 @@ void deletePreset(byte index);
bool getPresetName(byte index, String& name); bool getPresetName(byte index, String& name);
//remote.cpp //remote.cpp
void handleRemote(uint8_t *data, size_t len); void handleWiZdata(uint8_t *incomingData, size_t len);
void handleRemote();
//set.cpp //set.cpp
bool isAsterisksOnly(const char* str, byte maxLen); bool isAsterisksOnly(const char* str, byte maxLen);
@ -239,7 +346,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply=tru
//udp.cpp //udp.cpp
void notify(byte callMode, bool followUp=false); 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 realtimeLock(uint32_t timeoutMs, byte md = REALTIME_MODE_GENERIC);
void exitRealtime(); void exitRealtime();
void handleNotifications(); void handleNotifications();
@ -329,36 +436,33 @@ class Usermod {
#endif #endif
}; };
class UsermodManager { namespace UsermodManager {
private: extern byte numMods;
static Usermod* ums[WLED_MAX_USERMODS];
static byte numMods;
public: void loop();
static void loop(); void handleOverlayDraw();
static void handleOverlayDraw(); bool handleButton(uint8_t b);
static bool handleButton(uint8_t b); bool getUMData(um_data_t **um_data, uint8_t mod_id = USERMOD_ID_RESERVED); // USERMOD_ID_RESERVED will poll all usermods
static bool getUMData(um_data_t **um_data, uint8_t mod_id = USERMOD_ID_RESERVED); // USERMOD_ID_RESERVED will poll all usermods void setup();
static void setup(); void connected();
static void connected(); void appendConfigData(Print&);
static void appendConfigData(Print&); void addToJsonState(JsonObject& obj);
static void addToJsonState(JsonObject& obj); void addToJsonInfo(JsonObject& obj);
static void addToJsonInfo(JsonObject& obj); void readFromJsonState(JsonObject& obj);
static void readFromJsonState(JsonObject& obj); void addToConfig(JsonObject& obj);
static void addToConfig(JsonObject& obj); bool readFromConfig(JsonObject& obj);
static bool readFromConfig(JsonObject& obj);
#ifndef WLED_DISABLE_MQTT #ifndef WLED_DISABLE_MQTT
static void onMqttConnect(bool sessionPresent); void onMqttConnect(bool sessionPresent);
static bool onMqttMessage(char* topic, char* payload); bool onMqttMessage(char* topic, char* payload);
#endif #endif
#ifndef WLED_DISABLE_ESPNOW #ifndef WLED_DISABLE_ESPNOW
static bool onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t len); bool onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t len);
#endif #endif
static void onUpdateBegin(bool); void onUpdateBegin(bool);
static void onStateChange(uint8_t); void onStateChange(uint8_t);
static bool add(Usermod* um); bool add(Usermod* um);
static Usermod* lookup(uint16_t mod_id); Usermod* lookup(uint16_t mod_id);
static inline byte getModCount() {return numMods;}; inline byte getModCount() {return numMods;};
}; };
//usermods_list.cpp //usermods_list.cpp
@ -370,10 +474,16 @@ void userConnected();
void userLoop(); void userLoop();
//util.cpp //util.cpp
int getNumVal(const String* req, uint16_t pos); #ifdef ESP8266
#define HW_RND_REGISTER RANDOM_REG32
#else // ESP32 family
#include "soc/wdev_reg.h"
#define HW_RND_REGISTER REG_READ(WDEV_RND_REG)
#endif
[[gnu::pure]] int getNumVal(const String* req, uint16_t pos);
void parseNumber(const char* str, byte* val, byte minv=0, byte maxv=255); 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); bool getVal(JsonVariant elem, byte* val, byte vmin=0, byte vmax=255); // getVal supports inc/decrementing and random ("X~Y(r|[w]~[-][Z])" form)
bool getBoolVal(JsonVariant elem, bool dflt); [[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); 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 printSetFormCheckbox(Print& settingsScript, const char* key, int val);
size_t printSetFormValue(Print& settingsScript, const char* key, int val); size_t printSetFormValue(Print& settingsScript, const char* key, int val);
@ -381,18 +491,39 @@ size_t printSetFormValue(Print& settingsScript, const char* key, const char* val
size_t printSetFormIndex(Print& settingsScript, const char* key, int index); size_t printSetFormIndex(Print& settingsScript, const char* key, int index);
size_t printSetClassElementHTML(Print& settingsScript, const char* key, const int index, const char* val); size_t printSetClassElementHTML(Print& settingsScript, const char* key, const int index, const char* val);
void prepareHostname(char* hostname); void prepareHostname(char* hostname);
bool isAsterisksOnly(const char* str, byte maxLen); [[gnu::pure]] bool isAsterisksOnly(const char* str, byte maxLen);
bool requestJSONBufferLock(uint8_t module=255); bool requestJSONBufferLock(uint8_t moduleID=255);
void releaseJSONBufferLock(); void releaseJSONBufferLock();
uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLen); 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); uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxLen, uint8_t *var = nullptr);
int16_t extractModeDefaults(uint8_t mode, const char *segVar); int16_t extractModeDefaults(uint8_t mode, const char *segVar);
void checkSettingsPIN(const char *pin); void checkSettingsPIN(const char *pin);
uint16_t crc16(const unsigned char* data_p, size_t length); uint16_t crc16(const unsigned char* data_p, size_t length);
uint16_t beatsin88_t(accum88 beats_per_minute_88, uint16_t lowest = 0, uint16_t highest = 65535, uint32_t timebase = 0, uint16_t phase_offset = 0);
uint16_t beatsin16_t(accum88 beats_per_minute, uint16_t lowest = 0, uint16_t highest = 65535, uint32_t timebase = 0, uint16_t phase_offset = 0);
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); um_data_t* simulateSound(uint8_t simulationId);
void enumerateLedmaps(); void enumerateLedmaps();
uint8_t get_random_wheel_index(uint8_t pos); [[gnu::hot]] 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, 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)
// tests show it is still highly random reading it quickly in a loop (better than fastled PRNG)
// for 8bit and 16bit random functions: no limit check is done for best speed
// 32bit inputs are used for speed and code size, limits don't work if inverted or out of range
// inlining does save code size except for random(a,b) and 32bit random with limits
#define random hw_random // replace arduino random()
inline uint32_t hw_random() { return HW_RND_REGISTER; };
uint32_t hw_random(uint32_t upperlimit); // not inlined for code size
int32_t hw_random(int32_t lowerlimit, int32_t upperlimit);
inline uint16_t hw_random16() { return HW_RND_REGISTER; };
inline uint16_t hw_random16(uint32_t upperlimit) { return (hw_random16() * upperlimit) >> 16; }; // input range 0-65535 (uint16_t)
inline int16_t hw_random16(int32_t lowerlimit, int32_t upperlimit) { int32_t range = upperlimit - lowerlimit; return lowerlimit + hw_random16(range); }; // signed limits, use int16_t ranges
inline uint8_t hw_random8() { return HW_RND_REGISTER; };
inline uint8_t hw_random8(uint32_t upperlimit) { return (hw_random8() * upperlimit) >> 8; }; // input range 0-255
inline uint8_t hw_random8(uint32_t lowerlimit, uint32_t upperlimit) { uint32_t range = upperlimit - lowerlimit; return lowerlimit + hw_random8(range); }; // input range 0-255
// RAII guard class for the JSON Buffer lock // RAII guard class for the JSON Buffer lock
// Modeled after std::lock_guard // Modeled after std::lock_guard
@ -419,27 +550,38 @@ void clearEEPROM();
#endif #endif
//wled_math.cpp //wled_math.cpp
#if defined(ESP8266) && !defined(WLED_USE_REAL_MATH) //float cos_t(float phi); // use float math
template <typename T> T atan_t(T x); //float sin_t(float phi);
float cos_t(float phi); //float tan_t(float x);
float sin_t(float x); int16_t sin16_t(uint16_t theta);
float tan_t(float x); int16_t cos16_t(uint16_t theta);
float acos_t(float x); uint8_t sin8_t(uint8_t theta);
float asin_t(float x); uint8_t cos8_t(uint8_t theta);
float floor_t(float x); float sin_approx(float theta); // uses integer math (converted to float), accuracy +/-0.0015 (compared to sinf())
float fmod_t(float num, float denom); float cos_approx(float theta);
#else float tan_approx(float x);
#include <math.h> float atan2_t(float y, float x);
#define sin_t sinf float acos_t(float x);
#define cos_t cosf float asin_t(float x);
#define tan_t tanf template <typename T> T atan_t(T x);
#define asin_t asinf float floor_t(float x);
#define acos_t acosf float fmod_t(float num, float denom);
#define atan_t atanf uint32_t sqrt32_bw(uint32_t x);
#define fmod_t fmodf #define sin_t sin_approx
#define floor_t floorf #define cos_t cos_approx
#endif #define tan_t tan_approx
/*
#include <math.h> // standard math functions. use a lot of flash
#define sin_t sinf
#define cos_t cosf
#define tan_t tanf
#define asin_t asinf
#define acos_t acosf
#define atan_t atanf
#define fmod_t fmodf
#define floor_t floorf
*/
//wled_serial.cpp //wled_serial.cpp
void handleSerial(); void handleSerial();
void updateBaudRate(uint32_t rate); void updateBaudRate(uint32_t rate);

View File

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

144
wled00/image_loader.cpp Normal file
View File

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

View File

@ -129,7 +129,7 @@ static void changeEffectSpeed(int8_t amount)
} else { // if Effect == "solid Color", change the hue of the primary color } else { // if Effect == "solid Color", change the hue of the primary color
Segment& sseg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment(); Segment& sseg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment();
CRGB fastled_col = CRGB(sseg.colors[0]); CRGB fastled_col = CRGB(sseg.colors[0]);
CHSV prim_hsv = rgb2hsv_approximate(fastled_col); CHSV prim_hsv = rgb2hsv(fastled_col);
int16_t new_val = (int16_t)prim_hsv.h + amount; int16_t new_val = (int16_t)prim_hsv.h + amount;
if (new_val > 255) new_val -= 255; // roll-over if bigger than 255 if (new_val > 255) new_val -= 255; // roll-over if bigger than 255
if (new_val < 0) new_val += 255; // roll-over if smaller than 0 if (new_val < 0) new_val += 255; // roll-over if smaller than 0
@ -173,7 +173,7 @@ static void changeEffectIntensity(int8_t amount)
} else { // if Effect == "solid Color", change the saturation of the primary color } else { // if Effect == "solid Color", change the saturation of the primary color
Segment& sseg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment(); Segment& sseg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment();
CRGB fastled_col = CRGB(sseg.colors[0]); CRGB fastled_col = CRGB(sseg.colors[0]);
CHSV prim_hsv = rgb2hsv_approximate(fastled_col); CHSV prim_hsv = rgb2hsv(fastled_col);
int16_t new_val = (int16_t) prim_hsv.s + amount; int16_t new_val = (int16_t) prim_hsv.s + amount;
prim_hsv.s = (byte)constrain(new_val,0,255); // constrain to 0-255 prim_hsv.s = (byte)constrain(new_val,0,255); // constrain to 0-255
hsv2rgb_rainbow(prim_hsv, fastled_col); hsv2rgb_rainbow(prim_hsv, fastled_col);
@ -435,7 +435,7 @@ static void decodeIR44(uint32_t code)
case IR44_DIY2 : presetFallback(2, FX_MODE_BREATH, 0); break; case IR44_DIY2 : presetFallback(2, FX_MODE_BREATH, 0); break;
case IR44_DIY3 : presetFallback(3, FX_MODE_FIRE_FLICKER, 0); break; case IR44_DIY3 : presetFallback(3, FX_MODE_FIRE_FLICKER, 0); break;
case IR44_DIY4 : presetFallback(4, FX_MODE_RAINBOW, 0); break; case IR44_DIY4 : presetFallback(4, FX_MODE_RAINBOW, 0); break;
case IR44_DIY5 : presetFallback(5, FX_MODE_METEOR_SMOOTH, 0); break; case IR44_DIY5 : presetFallback(5, FX_MODE_METEOR, 0); break;
case IR44_DIY6 : presetFallback(6, FX_MODE_RAIN, 0); break; case IR44_DIY6 : presetFallback(6, FX_MODE_RAIN, 0); break;
case IR44_AUTO : changeEffect(FX_MODE_STATIC); break; case IR44_AUTO : changeEffect(FX_MODE_STATIC); break;
case IR44_FLASH : changeEffect(FX_MODE_PALETTE); break; case IR44_FLASH : changeEffect(FX_MODE_PALETTE); break;
@ -593,7 +593,7 @@ static void decodeIRJson(uint32_t code)
decBrightness(); decBrightness();
} else if (cmdStr.startsWith(F("!presetF"))) { //!presetFallback } else if (cmdStr.startsWith(F("!presetF"))) { //!presetFallback
uint8_t p1 = fdo["PL"] | 1; uint8_t p1 = fdo["PL"] | 1;
uint8_t p2 = fdo["FX"] | random8(strip.getModeCount() -1); uint8_t p2 = fdo["FX"] | hw_random8(strip.getModeCount() -1);
uint8_t p3 = fdo["FP"] | 0; uint8_t p3 = fdo["FP"] | 0;
presetFallback(p1, p2, p3); presetFallback(p1, p2, p3);
} }

View File

@ -34,7 +34,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
//DEBUG_PRINTLN(F("-- JSON deserialize segment.")); //DEBUG_PRINTLN(F("-- JSON deserialize segment."));
Segment& seg = strip.getSegment(id); Segment& seg = strip.getSegment(id);
//DEBUG_PRINTF_P(PSTR("-- Original segment: %p (%p)\n"), &seg, seg.data); //DEBUG_PRINTF_P(PSTR("-- Original segment: %p (%p)\n"), &seg, seg.data);
Segment prev = seg; //make a backup so we can tell if something changed (calling copy constructor) const Segment prev = seg; //make a backup so we can tell if something changed (calling copy constructor)
//DEBUG_PRINTF_P(PSTR("-- Duplicate segment: %p (%p)\n"), &prev, prev.data); //DEBUG_PRINTF_P(PSTR("-- Duplicate segment: %p (%p)\n"), &prev, prev.data);
int start = elem["start"] | seg.start; int start = elem["start"] | seg.start;
@ -68,7 +68,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
if (elem["n"]) { if (elem["n"]) {
// name field exists // name field exists
if (seg.name) { //clear old name if (seg.name) { //clear old name
delete[] seg.name; free(seg.name);
seg.name = nullptr; seg.name = nullptr;
} }
@ -77,7 +77,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
if (name != nullptr) len = strlen(name); if (name != nullptr) len = strlen(name);
if (len > 0) { if (len > 0) {
if (len > WLED_MAX_SEGNAME_LEN) len = WLED_MAX_SEGNAME_LEN; 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); if (seg.name) strlcpy(seg.name, name, WLED_MAX_SEGNAME_LEN+1);
} else { } else {
// but is empty (already deleted above) // 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) { } else if (start != seg.start || stop != seg.stop) {
// clearing or setting segment without name field // clearing or setting segment without name field
if (seg.name) { if (seg.name) {
delete[] seg.name; free(seg.name);
seg.name = nullptr; seg.name = nullptr;
} }
} }
@ -96,17 +96,11 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
uint16_t of = seg.offset; uint16_t of = seg.offset;
uint8_t soundSim = elem["si"] | seg.soundSim; uint8_t soundSim = elem["si"] | seg.soundSim;
uint8_t map1D2D = elem["m12"] | seg.map1D2D; uint8_t map1D2D = elem["m12"] | seg.map1D2D;
if ((spc>0 && spc!=seg.spacing) || seg.map1D2D!=map1D2D) seg.fill(BLACK); // clear spacing gaps
seg.map1D2D = constrain(map1D2D, 0, 7);
seg.soundSim = constrain(soundSim, 0, 3);
uint8_t set = elem[F("set")] | seg.set; uint8_t set = elem[F("set")] | seg.set;
seg.set = constrain(set, 0, 3); seg.set = constrain(set, 0, 3);
seg.soundSim = constrain(soundSim, 0, 3);
int len = 1; int len = (stop > start) ? stop - start : 1;
if (stop > start) len = stop - start;
int offset = elem[F("of")] | INT32_MAX; int offset = elem[F("of")] | INT32_MAX;
if (offset != INT32_MAX) { if (offset != INT32_MAX) {
int offsetAbs = abs(offset); int offsetAbs = abs(offset);
@ -117,7 +111,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
if (stop > start && of > len -1) of = len -1; if (stop > start && of > len -1) of = len -1;
// update segment (delete if necessary) // update segment (delete if necessary)
seg.setUp(start, stop, grp, spc, of, startY, stopY); // strip needs to be suspended for this to work without issues seg.setGeometry(start, stop, grp, spc, of, startY, stopY, map1D2D); // strip needs to be suspended for this to work without issues
if (newSeg) seg.refreshLightCapabilities(); // fix for #3403 if (newSeg) seg.refreshLightCapabilities(); // fix for #3403
@ -223,30 +217,17 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
#endif #endif
byte fx = seg.mode; byte fx = seg.mode;
byte last = strip.getModeCount(); if (getVal(elem["fx"], &fx, 0, strip.getModeCount())) {
// partial fix for #3605
if (!elem["fx"].isNull() && elem["fx"].is<const char*>()) {
const char *tmp = elem["fx"].as<const char *>();
if (strlen(tmp) > 3 && (strchr(tmp,'r') || strchr(tmp,'~') != strrchr(tmp,'~'))) last = 0; // we have "X~Y(r|[w]~[-])" form
}
// end fix
if (getVal(elem["fx"], &fx, 0, last)) { //load effect ('r' random, '~' inc/dec, 0-255 exact value, 5~10r pick random between 5 & 10)
if (!presetId && currentPlaylist>=0) unloadPlaylist(); if (!presetId && currentPlaylist>=0) unloadPlaylist();
if (fx != seg.mode) seg.setMode(fx, elem[F("fxdef")]); if (fx != seg.mode) seg.setMode(fx, elem[F("fxdef")]);
} }
//getVal also supports inc/decrementing and random
getVal(elem["sx"], &seg.speed); getVal(elem["sx"], &seg.speed);
getVal(elem["ix"], &seg.intensity); getVal(elem["ix"], &seg.intensity);
uint8_t pal = seg.palette; uint8_t pal = seg.palette;
last = strip.getPaletteCount();
if (!elem["pal"].isNull() && elem["pal"].is<const char*>()) {
const char *tmp = elem["pal"].as<const char *>();
if (strlen(tmp) > 3 && (strchr(tmp,'r') || strchr(tmp,'~') != strrchr(tmp,'~'))) last = 0; // we have "X~Y(r|[w]~[-])" form
}
if (seg.getLightCapabilities() & 1) { // ignore palette for White and On/Off segments if (seg.getLightCapabilities() & 1) { // ignore palette for White and On/Off segments
if (getVal(elem["pal"], &pal, 0, last)) seg.setPalette(pal); if (getVal(elem["pal"], &pal, 0, strip.getPaletteCount())) seg.setPalette(pal);
} }
getVal(elem["c1"], &seg.custom1); getVal(elem["c1"], &seg.custom1);
@ -351,15 +332,20 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
tr = root[F("transition")] | -1; tr = root[F("transition")] | -1;
if (tr >= 0) { if (tr >= 0) {
transitionDelay = tr * 100; 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) // temporary transition (applies only once)
tr = root[F("tt")] | -1; tr = root[F("tt")] | -1;
if (tr >= 0) { if (tr >= 0) {
jsonTransitionOnce = true; jsonTransitionOnce = true;
if (fadeTransition) strip.setTransition(tr * 100); strip.setTransition(tr * 100);
} }
tr = root[F("tb")] | -1; tr = root[F("tb")] | -1;
@ -467,7 +453,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
DEBUG_PRINTF_P(PSTR("Preset direct: %d\n"), currentPreset); DEBUG_PRINTF_P(PSTR("Preset direct: %d\n"), currentPreset);
} else if (!root["ps"].isNull()) { } else if (!root["ps"].isNull()) {
// we have "ps" call (i.e. from button or external API call) or "pd" that includes "ps" (i.e. from UI call) // we have "ps" call (i.e. from button or external API call) or "pd" that includes "ps" (i.e. from UI call)
if (root["win"].isNull() && getVal(root["ps"], &presetCycCurr, 0, 0) && presetCycCurr > 0 && presetCycCurr < 251 && presetCycCurr != currentPreset) { if (root["win"].isNull() && getVal(root["ps"], &presetCycCurr, 1, 250) && presetCycCurr > 0 && presetCycCurr < 251 && presetCycCurr != currentPreset) {
DEBUG_PRINTF_P(PSTR("Preset select: %d\n"), presetCycCurr); DEBUG_PRINTF_P(PSTR("Preset select: %d\n"), presetCycCurr);
// b) preset ID only or preset that does not change state (use embedded cycling limits if they exist in getVal()) // b) preset ID only or preset that does not change state (use embedded cycling limits if they exist in getVal())
applyPreset(presetCycCurr, callMode); // async load from file system (only preset ID was specified) applyPreset(presetCycCurr, callMode); // async load from file system (only preset ID was specified)
@ -512,7 +498,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
return stateResponse; 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; root["id"] = id;
if (segmentBounds) { if (segmentBounds) {
@ -587,6 +573,9 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme
root["on"] = (bri > 0); root["on"] = (bri > 0);
root["bri"] = briLast; root["bri"] = briLast;
root[F("transition")] = transitionDelay/100; //in 100ms root[F("transition")] = transitionDelay/100; //in 100ms
#ifndef WLED_DISABLE_MODE_BLEND
root[F("bs")] = blendingStyle;
#endif
} }
if (!forPreset) { if (!forPreset) {
@ -780,7 +769,7 @@ void serializeInfo(JsonObject root)
root[F("freeheap")] = ESP.getFreeHeap(); root[F("freeheap")] = ESP.getFreeHeap();
#if defined(ARDUINO_ARCH_ESP32) #if defined(ARDUINO_ARCH_ESP32)
if (psramSafe && psramFound()) root[F("psram")] = ESP.getFreePsram(); if (psramFound()) root[F("psram")] = ESP.getFreePsram();
#endif #endif
root[F("uptime")] = millis()/1000 + rolloverMillis*4294967; root[F("uptime")] = millis()/1000 + rolloverMillis*4294967;
@ -902,10 +891,7 @@ void serializePalettes(JsonObject root, int page)
setPaletteColors(curPalette, PartyColors_p); setPaletteColors(curPalette, PartyColors_p);
break; break;
case 1: //random case 1: //random
curPalette.add("r"); for (int j = 0; j < 4; j++) curPalette.add("r");
curPalette.add("r");
curPalette.add("r");
curPalette.add("r");
break; break;
case 2: //primary color only case 2: //primary color only
curPalette.add("c1"); curPalette.add("c1");
@ -922,53 +908,20 @@ void serializePalettes(JsonObject root, int page)
curPalette.add("c1"); curPalette.add("c1");
break; break;
case 5: //primary + secondary (+tertiary if not off), more distinct case 5: //primary + secondary (+tertiary if not off), more distinct
for (int j = 0; j < 5; j++) curPalette.add("c1");
for (int j = 0; j < 5; j++) curPalette.add("c2");
for (int j = 0; j < 5; j++) curPalette.add("c3");
curPalette.add("c1"); curPalette.add("c1");
curPalette.add("c1");
curPalette.add("c1");
curPalette.add("c1");
curPalette.add("c1");
curPalette.add("c2");
curPalette.add("c2");
curPalette.add("c2");
curPalette.add("c2");
curPalette.add("c2");
curPalette.add("c3");
curPalette.add("c3");
curPalette.add("c3");
curPalette.add("c3");
curPalette.add("c3");
curPalette.add("c1");
break;
case 6: //Party colors
setPaletteColors(curPalette, PartyColors_p);
break;
case 7: //Cloud colors
setPaletteColors(curPalette, CloudColors_p);
break;
case 8: //Lava colors
setPaletteColors(curPalette, LavaColors_p);
break;
case 9: //Ocean colors
setPaletteColors(curPalette, OceanColors_p);
break;
case 10: //Forest colors
setPaletteColors(curPalette, ForestColors_p);
break;
case 11: //Rainbow colors
setPaletteColors(curPalette, RainbowColors_p);
break;
case 12: //Rainbow stripe colors
setPaletteColors(curPalette, RainbowStripeColors_p);
break; break;
default: default:
{ if (i >= palettesCount)
if (i>=palettesCount) {
setPaletteColors(curPalette, strip.customPalettes[i - palettesCount]); setPaletteColors(curPalette, strip.customPalettes[i - palettesCount]);
} else { else if (i < 13) // palette 6 - 12, fastled palettes
setPaletteColors(curPalette, *fastledPalettes[i-6]);
else {
memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[i - 13])), 72); memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[i - 13])), 72);
setPaletteColors(curPalette, tcp); setPaletteColors(curPalette, tcp);
} }
}
break; break;
} }
} }
@ -1107,7 +1060,7 @@ void serveJson(AsyncWebServerRequest* request)
} }
if (!requestJSONBufferLock(17)) { if (!requestJSONBufferLock(17)) {
serveJsonError(request, 503, ERR_NOBUF); request->deferResponse();
return; return;
} }
// releaseJSONBufferLock() will be called when "response" is destroyed (from AsyncWebServer) // releaseJSONBufferLock() will be called when "response" is destroyed (from AsyncWebServer)

View File

@ -71,10 +71,10 @@ byte scaledBri(byte in)
} }
//applies global brightness //applies global temporary brightness (briT) to strip
void applyBri() { 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)); strip.setBrightness(scaledBri(briT));
} }
} }
@ -85,6 +85,7 @@ void applyFinalBri() {
briOld = bri; briOld = bri;
briT = bri; briT = bri;
applyBri(); applyBri();
strip.trigger(); // force one last update
} }
@ -128,31 +129,23 @@ void stateUpdated(byte callMode) {
// notify usermods of state change // notify usermods of state change
UsermodManager::onStateChange(callMode); UsermodManager::onStateChange(callMode);
if (fadeTransition) {
if (strip.getTransition() == 0) { if (strip.getTransition() == 0) {
jsonTransitionOnce = false; jsonTransitionOnce = false;
transitionActive = false; transitionActive = false;
applyFinalBri(); applyFinalBri();
strip.trigger();
return; return;
} }
if (transitionActive) { if (transitionActive) {
briOld = briT; briOld = briT;
tperLast = 0;
} else } else
strip.setTransitionMode(true); // force all segments to transition mode strip.setTransitionMode(true); // force all segments to transition mode
transitionActive = true; transitionActive = true;
transitionStartTime = millis(); transitionStartTime = millis();
} else {
applyFinalBri();
strip.trigger();
}
} }
void updateInterfaces(uint8_t callMode) void updateInterfaces(uint8_t callMode) {
{
if (!interfaceUpdateCallMode || millis() - lastInterfaceUpdate < INTERFACE_UPDATE_COOLDOWN) return; if (!interfaceUpdateCallMode || millis() - lastInterfaceUpdate < INTERFACE_UPDATE_COOLDOWN) return;
sendDataWs(); sendDataWs();
@ -173,28 +166,26 @@ void updateInterfaces(uint8_t callMode)
} }
void handleTransitions() void handleTransitions() {
{
//handle still pending interface update //handle still pending interface update
updateInterfaces(interfaceUpdateCallMode); updateInterfaces(interfaceUpdateCallMode);
if (transitionActive && strip.getTransition() > 0) { if (transitionActive && strip.getTransition() > 0) {
float tper = (millis() - transitionStartTime)/(float)strip.getTransition(); int ti = millis() - transitionStartTime;
if (tper >= 1.0f) { int tr = strip.getTransition();
if (ti/tr) {
strip.setTransitionMode(false); // stop all transitions strip.setTransitionMode(false); // stop all transitions
// restore (global) transition time if not called from UDP notifier or single/temporary transition from JSON (also playlist) // restore (global) transition time if not called from UDP notifier or single/temporary transition from JSON (also playlist)
if (jsonTransitionOnce) strip.setTransition(transitionDelay); if (jsonTransitionOnce) strip.setTransition(transitionDelay);
transitionActive = false; transitionActive = false;
jsonTransitionOnce = false; jsonTransitionOnce = false;
tperLast = 0;
applyFinalBri(); applyFinalBri();
return; return;
} }
if (tper - tperLast < 0.004f) return; // less than 1 bit change (1/255) byte briTO = briT;
tperLast = tper; int deltaBri = (int)bri - (int)briOld;
briT = briOld + ((bri - briOld) * tper); briT = briOld + (deltaBri * ti / tr);
if (briTO != briT) applyBri();
applyBri();
} }
} }
@ -206,8 +197,7 @@ void colorUpdated(byte callMode) {
} }
void handleNightlight() void handleNightlight() {
{
unsigned long now = millis(); unsigned long now = millis();
if (now < 100 && lastNlUpdate > 0) lastNlUpdate = 0; // take care of millis() rollover if (now < 100 && lastNlUpdate > 0) lastNlUpdate = 0; // take care of millis() rollover
if (now - lastNlUpdate < 100) return; // allow only 10 NL updates per second if (now - lastNlUpdate < 100) return; // allow only 10 NL updates per second
@ -229,8 +219,8 @@ void handleNightlight()
colNlT[1] = effectSpeed; colNlT[1] = effectSpeed;
colNlT[2] = effectPalette; colNlT[2] = effectPalette;
strip.setMode(strip.getFirstSelectedSegId(), FX_MODE_STATIC); // make sure seg runtime is reset if it was in sunrise mode strip.getFirstSelectedSeg().setMode(FX_MODE_STATIC); // make sure seg runtime is reset if it was in sunrise mode
effectCurrent = FX_MODE_SUNRISE; effectCurrent = FX_MODE_SUNRISE; // colorUpdated() will take care of assigning that to all selected segments
effectSpeed = nightlightDelayMins; effectSpeed = nightlightDelayMins;
effectPalette = 0; effectPalette = 0;
if (effectSpeed > 60) effectSpeed = 60; //currently limited to 60 minutes if (effectSpeed > 60) effectSpeed = 60; //currently limited to 60 minutes
@ -287,7 +277,6 @@ void handleNightlight()
} }
//utility for FastLED to use our custom timer //utility for FastLED to use our custom timer
uint32_t get_millisecond_timer() uint32_t get_millisecond_timer() {
{
return strip.now; return strip.now;
} }

View File

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

View File

@ -7,6 +7,10 @@
#ifndef WLED_DISABLE_MQTT #ifndef WLED_DISABLE_MQTT
#define MQTT_KEEP_ALIVE_TIME 60 // contact the MQTT broker every 60 seconds #define MQTT_KEEP_ALIVE_TIME 60 // contact the MQTT broker every 60 seconds
#if MQTT_MAX_TOPIC_LEN > 32
#warning "MQTT topics length > 32 is not recommended for compatibility with usermods!"
#endif
static void parseMQTTBriPayload(char* payload) static void parseMQTTBriPayload(char* payload)
{ {
if (strstr(payload, "ON") || strstr(payload, "on") || strstr(payload, "true")) {bri = briLast; stateUpdated(CALL_MODE_DIRECT_CHANGE);} if (strstr(payload, "ON") || strstr(payload, "on") || strstr(payload, "true")) {bri = briLast; stateUpdated(CALL_MODE_DIRECT_CHANGE);}
@ -23,24 +27,24 @@ static void parseMQTTBriPayload(char* payload)
static void onMqttConnect(bool sessionPresent) static void onMqttConnect(bool sessionPresent)
{ {
//(re)subscribe to required topics //(re)subscribe to required topics
char subuf[38]; char subuf[MQTT_MAX_TOPIC_LEN + 6];
if (mqttDeviceTopic[0] != 0) { if (mqttDeviceTopic[0] != 0) {
strlcpy(subuf, mqttDeviceTopic, 33); strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1);
mqtt->subscribe(subuf, 0); mqtt->subscribe(subuf, 0);
strcat_P(subuf, PSTR("/col")); strcat_P(subuf, PSTR("/col"));
mqtt->subscribe(subuf, 0); mqtt->subscribe(subuf, 0);
strlcpy(subuf, mqttDeviceTopic, 33); strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1);
strcat_P(subuf, PSTR("/api")); strcat_P(subuf, PSTR("/api"));
mqtt->subscribe(subuf, 0); mqtt->subscribe(subuf, 0);
} }
if (mqttGroupTopic[0] != 0) { if (mqttGroupTopic[0] != 0) {
strlcpy(subuf, mqttGroupTopic, 33); strlcpy(subuf, mqttGroupTopic, MQTT_MAX_TOPIC_LEN + 1);
mqtt->subscribe(subuf, 0); mqtt->subscribe(subuf, 0);
strcat_P(subuf, PSTR("/col")); strcat_P(subuf, PSTR("/col"));
mqtt->subscribe(subuf, 0); mqtt->subscribe(subuf, 0);
strlcpy(subuf, mqttGroupTopic, 33); strlcpy(subuf, mqttGroupTopic, MQTT_MAX_TOPIC_LEN + 1);
strcat_P(subuf, PSTR("/api")); strcat_P(subuf, PSTR("/api"));
mqtt->subscribe(subuf, 0); mqtt->subscribe(subuf, 0);
} }
@ -64,8 +68,8 @@ static void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProp
} }
if (index == 0) { // start (1st partial packet or the only packet) if (index == 0) { // start (1st partial packet or the only packet)
if (payloadStr) delete[] payloadStr; // fail-safe: release buffer if (payloadStr) free(payloadStr); // fail-safe: release buffer
payloadStr = new char[total+1]; // allocate new buffer payloadStr = static_cast<char*>(malloc(total+1)); // allocate new buffer
} }
if (payloadStr == nullptr) return; // buffer not allocated if (payloadStr == nullptr) return; // buffer not allocated
@ -90,7 +94,7 @@ static void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProp
} else { } else {
// Non-Wled Topic used here. Probably a usermod subscribed to this topic. // Non-Wled Topic used here. Probably a usermod subscribed to this topic.
UsermodManager::onMqttMessage(topic, payloadStr); UsermodManager::onMqttMessage(topic, payloadStr);
delete[] payloadStr; free(payloadStr);
payloadStr = nullptr; payloadStr = nullptr;
return; return;
} }
@ -120,7 +124,7 @@ static void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProp
// topmost topic (just wled/MAC) // topmost topic (just wled/MAC)
parseMQTTBriPayload(payloadStr); parseMQTTBriPayload(payloadStr);
} }
delete[] payloadStr; free(payloadStr);
payloadStr = nullptr; payloadStr = nullptr;
} }
@ -158,19 +162,19 @@ void publishMqtt()
#ifndef USERMOD_SMARTNEST #ifndef USERMOD_SMARTNEST
char s[10]; char s[10];
char subuf[48]; char subuf[MQTT_MAX_TOPIC_LEN + 16];
sprintf_P(s, PSTR("%u"), bri); sprintf_P(s, PSTR("%u"), bri);
strlcpy(subuf, mqttDeviceTopic, 33); strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1);
strcat_P(subuf, PSTR("/g")); strcat_P(subuf, PSTR("/g"));
mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263) mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263)
sprintf_P(s, PSTR("#%06X"), (col[3] << 24) | (col[0] << 16) | (col[1] << 8) | (col[2])); sprintf_P(s, PSTR("#%06X"), (col[3] << 24) | (col[0] << 16) | (col[1] << 8) | (col[2]));
strlcpy(subuf, mqttDeviceTopic, 33); strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1);
strcat_P(subuf, PSTR("/c")); strcat_P(subuf, PSTR("/c"));
mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263) mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263)
strlcpy(subuf, mqttDeviceTopic, 33); strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1);
strcat_P(subuf, PSTR("/status")); strcat_P(subuf, PSTR("/status"));
mqtt->publish(subuf, 0, true, "online"); // retain message for a LWT mqtt->publish(subuf, 0, true, "online"); // retain message for a LWT
@ -178,7 +182,7 @@ void publishMqtt()
DynamicBuffer buf(1024); DynamicBuffer buf(1024);
bufferPrint pbuf(buf.data(), buf.size()); bufferPrint pbuf(buf.data(), buf.size());
XML_response(pbuf); XML_response(pbuf);
strlcpy(subuf, mqttDeviceTopic, 33); strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1);
strcat_P(subuf, PSTR("/v")); strcat_P(subuf, PSTR("/v"));
mqtt->publish(subuf, 0, retainMqttMsg, buf.data(), pbuf.size()); // optionally retain message (#2263) mqtt->publish(subuf, 0, retainMqttMsg, buf.data(), pbuf.size()); // optionally retain message (#2263)
#endif #endif
@ -211,7 +215,7 @@ bool initMqtt()
if (mqttUser[0] && mqttPass[0]) mqtt->setCredentials(mqttUser, mqttPass); if (mqttUser[0] && mqttPass[0]) mqtt->setCredentials(mqttUser, mqttPass);
#ifndef USERMOD_SMARTNEST #ifndef USERMOD_SMARTNEST
strlcpy(mqttStatusTopic, mqttDeviceTopic, 33); strlcpy(mqttStatusTopic, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1);
strcat_P(mqttStatusTopic, PSTR("/status")); strcat_P(mqttStatusTopic, PSTR("/status"));
mqtt->setWill(mqttStatusTopic, 0, true, "offline"); // LWT message mqtt->setWill(mqttStatusTopic, 0, true, "offline"); // LWT message
#endif #endif

View File

@ -207,6 +207,7 @@ void WiFiEvent(WiFiEvent_t event)
break; break;
#endif #endif
default: default:
DEBUG_PRINTF_P(PSTR("Network event: %d\n"), (int)event);
break; break;
} }
} }

View File

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

View File

@ -1,5 +1,6 @@
/* /*
* Color palettes for FastLED effects (65-73). * Color palettes for FastLED effects (65-73).
* 4 bytes per color: index, red, green, blue
*/ */
// From ColorWavesWithPalettes by Mark Kriegsman: https://gist.github.com/kriegsman/8281905786e8b2632aeb // From ColorWavesWithPalettes by Mark Kriegsman: https://gist.github.com/kriegsman/8281905786e8b2632aeb
@ -844,6 +845,23 @@ const byte candy2_gp[] PROGMEM = {
211, 39, 33, 34, 211, 39, 33, 34,
255, 1, 1, 1}; 255, 1, 1, 1};
const byte trafficlight_gp[] PROGMEM = {
0, 0, 0, 0, //black
85, 0, 255, 0, //green
170, 255, 255, 0, //yellow
255, 255, 0, 0}; //red
// array of fastled palettes (palette 6 - 12)
const TProgmemRGBPalette16 *const fastledPalettes[] PROGMEM = {
&PartyColors_p, //06-00 Party
&CloudColors_p, //07-01 Cloud
&LavaColors_p, //08-02 Lava
&OceanColors_p, //09-03 Ocean
&ForestColors_p, //10-04 Forest
&RainbowColors_p, //11-05 Rainbow
&RainbowStripeColors_p //12-06 Rainbow Bands
};
// Single array of defined cpt-city color palettes. // Single array of defined cpt-city color palettes.
// This will let us programmatically choose one based on // This will let us programmatically choose one based on
// a number, rather than having to activate each explicitly // a number, rather than having to activate each explicitly
@ -906,7 +924,8 @@ const byte* const gGradientPalettes[] PROGMEM = {
blink_red_gp, //67-54 Blink Red blink_red_gp, //67-54 Blink Red
red_shift_gp, //68-55 Red Shift red_shift_gp, //68-55 Red Shift
red_tide_gp, //69-56 Red Tide red_tide_gp, //69-56 Red Tide
candy2_gp //70-57 Candy2 candy2_gp, //70-57 Candy2
trafficlight_gp //71-58 Traffic Light
}; };
#endif #endif

View File

@ -13,6 +13,16 @@
#endif #endif
#endif #endif
// Pin management state variables
#ifdef ESP8266
static uint32_t pinAlloc = 0UL; // 1 bit per pin, we use first 17bits
#else
static uint64_t pinAlloc = 0ULL; // 1 bit per pin, we use 50 bits on ESP32-S3
static uint16_t ledcAlloc = 0; // up to 16 LEDC channels (WLED_MAX_ANALOG_CHANNELS)
#endif
static uint8_t i2cAllocCount = 0; // allow multiple allocation of I2C bus pins but keep track of allocations
static uint8_t spiAllocCount = 0; // allow multiple allocation of SPI bus pins but keep track of allocations
static PinOwner ownerTag[WLED_NUM_PINS] = { PinOwner::None };
/// Actual allocation/deallocation routines /// Actual allocation/deallocation routines
bool PinManager::deallocatePin(byte gpio, PinOwner tag) bool PinManager::deallocatePin(byte gpio, PinOwner tag)
@ -131,7 +141,9 @@ bool PinManager::allocateMultiplePins(const managed_pin_type * mptArray, byte ar
bool PinManager::allocatePin(byte gpio, bool output, PinOwner tag) 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 // 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 #ifdef WLED_DEBUG
if (gpio < 255) { // 255 (-1) is the "not defined GPIO" if (gpio < 255) { // 255 (-1) is the "not defined GPIO"
if (!isPinOk(gpio, output)) { if (!isPinOk(gpio, output)) {
@ -214,8 +226,20 @@ bool PinManager::isPinOk(byte gpio, bool output)
// JTAG: GPIO39-42 are usually used for inline debugging // JTAG: GPIO39-42 are usually used for inline debugging
// GPIO46 is input only and pulled down // GPIO46 is input only and pulled down
#else #else
if ((strncmp_P(PSTR("ESP32-U4WDH"), ESP.getChipModel(), 11) == 0) || // this is the correct identifier, but....
(strncmp_P(PSTR("ESP32-PICO-D2"), ESP.getChipModel(), 13) == 0)) { // https://github.com/espressif/arduino-esp32/issues/10683
// this chip has 4 MB of internal Flash and different packaging, so available pins are different!
if (((gpio > 5) && (gpio < 9)) || (gpio == 11))
return false;
} else {
// for classic ESP32 (non-mini) modules, these are the SPI flash pins
if (gpio > 5 && gpio < 12) return false; //SPI flash pins if (gpio > 5 && gpio < 12) return false; //SPI flash pins
if (strncmp_P(PSTR("ESP32-PICO"), ESP.getChipModel(), 10) == 0 && (gpio == 16 || gpio == 17)) return false; // PICO-D4: gpio16+17 are in use for onboard SPI FLASH }
if (((strncmp_P(PSTR("ESP32-PICO"), ESP.getChipModel(), 10) == 0) ||
(strncmp_P(PSTR("ESP32-U4WDH"), ESP.getChipModel(), 11) == 0))
&& (gpio == 16 || gpio == 17)) return false; // PICO-D4/U4WDH: gpio16+17 are in use for onboard SPI FLASH
if (gpio == 16 || gpio == 17) return !psramFound(); //PSRAM pins on ESP32 (these are IO) if (gpio == 16 || gpio == 17) return !psramFound(); //PSRAM pins on ESP32 (these are IO)
#endif #endif
if (output) return digitalPinCanOutput(gpio); if (output) return digitalPinCanOutput(gpio);
@ -278,13 +302,3 @@ void PinManager::deallocateLedc(byte pos, byte channels)
} }
} }
#endif #endif
#ifdef ESP8266
uint32_t PinManager::pinAlloc = 0UL;
#else
uint64_t PinManager::pinAlloc = 0ULL;
uint16_t PinManager::ledcAlloc = 0;
#endif
uint8_t PinManager::i2cAllocCount = 0;
uint8_t PinManager::spiAllocCount = 0;
PinOwner PinManager::ownerTag[WLED_NUM_PINS] = { PinOwner::None };

View File

@ -9,6 +9,12 @@
#endif #endif
#include "const.h" // for USERMOD_* values #include "const.h" // for USERMOD_* values
#ifdef ESP8266
#define WLED_NUM_PINS (GPIO_PIN_COUNT+1) // somehow they forgot GPIO 16 (0-16==17)
#else
#define WLED_NUM_PINS (GPIO_PIN_COUNT)
#endif
typedef struct PinManagerPinType { typedef struct PinManagerPinType {
int8_t pin; int8_t pin;
bool isOutput; bool isOutput;
@ -38,6 +44,7 @@ enum struct PinOwner : uint8_t {
DMX = 0x8A, // 'DMX' == hard-coded to IO2 DMX = 0x8A, // 'DMX' == hard-coded to IO2
HW_I2C = 0x8B, // 'I2C' == hardware I2C pins (4&5 on ESP8266, 21&22 on ESP32) 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) 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 // Use UserMod IDs from const.h here
UM_Unspecified = USERMOD_ID_UNSPECIFIED, // 0x01 UM_Unspecified = USERMOD_ID_UNSPECIFIED, // 0x01
UM_Example = USERMOD_ID_EXAMPLE, // 0x02 // Usermod "usermod_v2_example.h" UM_Example = USERMOD_ID_EXAMPLE, // 0x02 // Usermod "usermod_v2_example.h"
@ -70,52 +77,38 @@ enum struct PinOwner : uint8_t {
}; };
static_assert(0u == static_cast<uint8_t>(PinOwner::None), "PinOwner::None must be zero, so default array initialization works as expected"); static_assert(0u == static_cast<uint8_t>(PinOwner::None), "PinOwner::None must be zero, so default array initialization works as expected");
class PinManager { namespace PinManager {
private:
#ifdef ESP8266
#define WLED_NUM_PINS (GPIO_PIN_COUNT+1) // somehow they forgot GPIO 16 (0-16==17)
static uint32_t pinAlloc; // 1 bit per pin, we use first 17bits
#else
#define WLED_NUM_PINS (GPIO_PIN_COUNT)
static uint64_t pinAlloc; // 1 bit per pin, we use 50 bits on ESP32-S3
static uint16_t ledcAlloc; // up to 16 LEDC channels (WLED_MAX_ANALOG_CHANNELS)
#endif
static uint8_t i2cAllocCount; // allow multiple allocation of I2C bus pins but keep track of allocations
static uint8_t spiAllocCount; // allow multiple allocation of SPI bus pins but keep track of allocations
static PinOwner ownerTag[WLED_NUM_PINS];
public:
// De-allocates a single pin // De-allocates a single pin
static bool deallocatePin(byte gpio, PinOwner tag); bool deallocatePin(byte gpio, PinOwner tag);
// De-allocates multiple pins but only if all can be deallocated (PinOwner has to be specified) // De-allocates multiple pins but only if all can be deallocated (PinOwner has to be specified)
static bool deallocateMultiplePins(const uint8_t *pinArray, byte arrayElementCount, PinOwner tag); bool deallocateMultiplePins(const uint8_t *pinArray, byte arrayElementCount, PinOwner tag);
static bool deallocateMultiplePins(const managed_pin_type *pinArray, byte arrayElementCount, PinOwner tag); bool deallocateMultiplePins(const managed_pin_type *pinArray, byte arrayElementCount, PinOwner tag);
// Allocates a single pin, with an owner tag. // Allocates a single pin, with an owner tag.
// De-allocation requires the same owner tag (or override) // De-allocation requires the same owner tag (or override)
static bool allocatePin(byte gpio, bool output, PinOwner tag); bool allocatePin(byte gpio, bool output, PinOwner tag);
// Allocates all the pins, or allocates none of the pins, with owner tag. // Allocates all the pins, or allocates none of the pins, with owner tag.
// Provided to simplify error condition handling in clients // Provided to simplify error condition handling in clients
// using more than one pin, such as I2C, SPI, rotary encoders, // using more than one pin, such as I2C, SPI, rotary encoders,
// ethernet, etc.. // ethernet, etc..
static bool allocateMultiplePins(const managed_pin_type * mptArray, byte arrayElementCount, PinOwner tag ); bool allocateMultiplePins(const managed_pin_type * mptArray, byte arrayElementCount, PinOwner tag );
[[deprecated("Replaced by three-parameter allocatePin(gpio, output, ownerTag), for improved debugging")]] [[deprecated("Replaced by three-parameter allocatePin(gpio, output, ownerTag), for improved debugging")]]
static inline bool allocatePin(byte gpio, bool output = true) { return allocatePin(gpio, output, PinOwner::None); } inline bool allocatePin(byte gpio, bool output = true) { return allocatePin(gpio, output, PinOwner::None); }
[[deprecated("Replaced by two-parameter deallocatePin(gpio, ownerTag), for improved debugging")]] [[deprecated("Replaced by two-parameter deallocatePin(gpio, ownerTag), for improved debugging")]]
static inline void deallocatePin(byte gpio) { deallocatePin(gpio, PinOwner::None); } inline void deallocatePin(byte gpio) { deallocatePin(gpio, PinOwner::None); }
// will return true for reserved pins // will return true for reserved pins
static bool isPinAllocated(byte gpio, PinOwner tag = PinOwner::None); bool isPinAllocated(byte gpio, PinOwner tag = PinOwner::None);
// will return false for reserved pins // will return false for reserved pins
static bool isPinOk(byte gpio, bool output = true); bool isPinOk(byte gpio, bool output = true);
static bool isReadOnlyPin(byte gpio); bool isReadOnlyPin(byte gpio);
static PinOwner getPinOwner(byte gpio); PinOwner getPinOwner(byte gpio);
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32
static byte allocateLedc(byte channels); byte allocateLedc(byte channels);
static void deallocateLedc(byte pos, byte channels); void deallocateLedc(byte pos, byte channels);
#endif #endif
}; };

View File

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

View File

@ -76,8 +76,8 @@ static void doSaveState() {
// clean up // clean up
saveLedmap = -1; saveLedmap = -1;
presetToSave = 0; presetToSave = 0;
delete[] saveName; free(saveName);
delete[] quickLoad; free(quickLoad);
saveName = nullptr; saveName = nullptr;
quickLoad = nullptr; quickLoad = nullptr;
playlistSave = false; playlistSave = false;
@ -164,6 +164,11 @@ void handlePresets()
DEBUG_PRINTF_P(PSTR("Applying preset: %u\n"), (unsigned)tmpPreset); DEBUG_PRINTF_P(PSTR("Applying preset: %u\n"), (unsigned)tmpPreset);
#if defined(ARDUINO_ARCH_ESP32S3) || defined(ARDUINO_ARCH_ESP32S2) || defined(ARDUINO_ARCH_ESP32C3)
unsigned long start = millis();
while (strip.isUpdating() && millis() - start < FRAMETIME_FIXED) yield(); // wait for strip to finish updating, accessing FS during sendout causes glitches
#endif
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32
if (tmpPreset==255 && tmpRAMbuffer!=nullptr) { if (tmpPreset==255 && tmpRAMbuffer!=nullptr) {
deserializeJson(*pDoc,tmpRAMbuffer); deserializeJson(*pDoc,tmpRAMbuffer);
@ -211,8 +216,8 @@ void handlePresets()
//called from handleSet(PS=) [network callback (sObj is empty), IR (irrational), deserializeState, UDP] and deserializeState() [network callback (filedoc!=nullptr)] //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) void savePreset(byte index, const char* pname, JsonObject sObj)
{ {
if (!saveName) saveName = new char[33]; if (!saveName) saveName = static_cast<char*>(malloc(33));
if (!quickLoad) quickLoad = new char[9]; if (!quickLoad) quickLoad = static_cast<char*>(malloc(9));
if (!saveName || !quickLoad) return; if (!saveName || !quickLoad) return;
if (index == 0 || (index > 250 && index < 255)) return; if (index == 0 || (index > 250 && index < 255)) return;
@ -258,8 +263,8 @@ void savePreset(byte index, const char* pname, JsonObject sObj)
presetsModifiedTime = toki.second(); //unix time presetsModifiedTime = toki.second(); //unix time
updateFSInfo(); updateFSInfo();
} }
delete[] saveName; free(saveName);
delete[] quickLoad; free(quickLoad);
saveName = nullptr; saveName = nullptr;
quickLoad = nullptr; quickLoad = nullptr;
} else { } else {

View File

@ -1,6 +1,8 @@
#include "wled.h" #include "wled.h"
#ifndef WLED_DISABLE_ESPNOW #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_DEACTIVATED -1
#define NIGHT_MODE_BRIGHTNESS 5 #define NIGHT_MODE_BRIGHTNESS 5
@ -38,6 +40,7 @@ typedef struct WizMoteMessageStructure {
static uint32_t last_seq = UINT32_MAX; static uint32_t last_seq = UINT32_MAX;
static int brightnessBeforeNightMode = NIGHT_MODE_DEACTIVATED; 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 // Pulled from the IR Remote logic but reduced to 10 steps with a constant of 3
static const byte brightnessSteps[] = { static const byte brightnessSteps[] = {
@ -121,6 +124,9 @@ static bool remoteJson(int button)
sprintf_P(objKey, PSTR("\"%d\":"), 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 // attempt to read command from remote.json
readObjectFromFile(PSTR("/remote.json"), objKey, pDoc); readObjectFromFile(PSTR("/remote.json"), objKey, pDoc);
JsonObject fdo = pDoc->as<JsonObject>(); JsonObject fdo = pDoc->as<JsonObject>();
@ -146,7 +152,7 @@ static bool remoteJson(int button)
parsed = true; parsed = true;
} else if (cmdStr.startsWith(F("!presetF"))) { //!presetFallback } else if (cmdStr.startsWith(F("!presetF"))) { //!presetFallback
uint8_t p1 = fdo["PL"] | 1; uint8_t p1 = fdo["PL"] | 1;
uint8_t p2 = fdo["FX"] | random8(strip.getModeCount() -1); uint8_t p2 = fdo["FX"] | hw_random8(strip.getModeCount() -1);
uint8_t p3 = fdo["FP"] | 0; uint8_t p3 = fdo["FP"] | 0;
presetWithFallback(p1, p2, p3); presetWithFallback(p1, p2, p3);
parsed = true; parsed = true;
@ -176,7 +182,7 @@ static bool remoteJson(int button)
} }
// Callback function that will be executed when data is received // 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); message_structure_t *incoming = reinterpret_cast<message_structure_t *>(incomingData);
if (strcmp(last_signal_src, linked_remote) != 0) { 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_PRINT(F("] button: "));
DEBUG_PRINTLN(incoming->button); DEBUG_PRINTLN(incoming->button);
if (!remoteJson(incoming->button)) ESPNowButton = incoming->button; // save state, do not process in callback (can cause glitches)
switch (incoming->button) { 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_ON : setOn(); break;
case WIZMOTE_BUTTON_OFF : setOff(); break; case WIZMOTE_BUTTON_OFF : setOff(); break;
case WIZMOTE_BUTTON_ONE : presetWithFallback(1, FX_MODE_STATIC, 0); 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; case WIZ_SMART_BUTTON_BRIGHT_DOWN : brightnessDown(); break;
default: break; default: break;
} }
last_seq = cur_seq; }
ESPNowButton = -1;
} }
#else #else
void handleRemote(uint8_t *incomingData, size_t len) {} void handleRemote() {}
#endif #endif

View File

@ -134,8 +134,8 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
strip.correctWB = request->hasArg(F("CCT")); strip.correctWB = request->hasArg(F("CCT"));
strip.cctFromRgb = request->hasArg(F("CR")); strip.cctFromRgb = request->hasArg(F("CR"));
cctICused = request->hasArg(F("IC")); cctICused = request->hasArg(F("IC"));
strip.cctBlending = request->arg(F("CB")).toInt(); uint8_t cctBlending = request->arg(F("CB")).toInt();
Bus::setCCTBlend(strip.cctBlending); Bus::setCCTBlend(cctBlending);
Bus::setGlobalAWMode(request->arg(F("AW")).toInt()); Bus::setGlobalAWMode(request->arg(F("AW")).toInt());
strip.setTargetFps(request->arg(F("FR")).toInt()); strip.setTargetFps(request->arg(F("FR")).toInt());
useGlobalLedBuffer = request->hasArg(F("LD")); useGlobalLedBuffer = request->hasArg(F("LD"));
@ -209,12 +209,13 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
// actual finalization is done in WLED::loop() (removing old busses and adding new) // 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 // this may happen even before this loop is finished so we do "doInitBusses" after the loop
if (busConfigs[s] != nullptr) delete busConfigs[s]; 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; busesChanged = true;
} }
//doInitBusses = busesChanged; // we will do that below to ensure all input data is processed //doInitBusses = busesChanged; // we will do that below to ensure all input data is processed
// we will not bother with pre-allocating ColorOrderMappings vector // we will not bother with pre-allocating ColorOrderMappings vector
BusManager::getColorOrderMap().reset();
for (int s = 0; s < WLED_MAX_COLOR_ORDER_MAPPINGS; s++) { for (int s = 0; s < WLED_MAX_COLOR_ORDER_MAPPINGS; s++) {
int offset = s < 10 ? 48 : 55; int offset = s < 10 ? 48 : 55;
char xs[4] = "XS"; xs[2] = offset+s; xs[3] = 0; //start LED char xs[4] = "XS"; xs[2] = offset+s; xs[3] = 0; //start LED
@ -318,19 +319,15 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
gammaCorrectBri = request->hasArg(F("GB")); gammaCorrectBri = request->hasArg(F("GB"));
gammaCorrectCol = request->hasArg(F("GC")); gammaCorrectCol = request->hasArg(F("GC"));
gammaCorrectVal = request->arg(F("GV")).toFloat(); gammaCorrectVal = request->arg(F("GV")).toFloat();
if (gammaCorrectVal > 1.0f && gammaCorrectVal <= 3) if (gammaCorrectVal <= 1.0f || gammaCorrectVal > 3) {
NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal);
else {
gammaCorrectVal = 1.0f; // no gamma correction gammaCorrectVal = 1.0f; // no gamma correction
gammaCorrectBri = false; gammaCorrectBri = false;
gammaCorrectCol = false; gammaCorrectCol = false;
} }
NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal); // fill look-up table
fadeTransition = request->hasArg(F("TF"));
modeBlending = request->hasArg(F("EB"));
t = request->arg(F("TD")).toInt(); t = request->arg(F("TD")).toInt();
if (t >= 0) transitionDelayDefault = t; if (t >= 0) transitionDelayDefault = t;
strip.paletteFade = request->hasArg(F("PF"));
t = request->arg(F("TP")).toInt(); t = request->arg(F("TP")).toInt();
randomPaletteChangeTime = MIN(255,MAX(1,t)); randomPaletteChangeTime = MIN(255,MAX(1,t));
useHarmonicRandomPalette = request->hasArg(F("TH")); useHarmonicRandomPalette = request->hasArg(F("TH"));
@ -420,6 +417,14 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
t = request->arg(F("WO")).toInt(); t = request->arg(F("WO")).toInt();
if (t >= -255 && t <= 255) arlsOffset = t; 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 #ifndef WLED_DISABLE_ALEXA
alexaEnabled = request->hasArg(F("AL")); alexaEnabled = request->hasArg(F("AL"));
strlcpy(alexaInvocationName, request->arg(F("AI")).c_str(), 33); strlcpy(alexaInvocationName, request->arg(F("AI")).c_str(), 33);
@ -623,7 +628,10 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
//USERMODS //USERMODS
if (subPage == SUBPAGE_UM) if (subPage == SUBPAGE_UM)
{ {
if (!requestJSONBufferLock(5)) return; if (!requestJSONBufferLock(5)) {
request->deferResponse();
return;
}
// global I2C & SPI pins // global I2C & SPI pins
int8_t hw_sda_pin = !request->arg(F("SDA")).length() ? -1 : (int)request->arg(F("SDA")).toInt(); int8_t hw_sda_pin = !request->arg(F("SDA")).length() ? -1 : (int)request->arg(F("SDA")).toInt();
@ -840,6 +848,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
// temporary values, write directly to segments, globals are updated by setValuesFromFirstSelectedSeg() // temporary values, write directly to segments, globals are updated by setValuesFromFirstSelectedSeg()
uint32_t col0 = selseg.colors[0]; uint32_t col0 = selseg.colors[0];
uint32_t col1 = selseg.colors[1]; uint32_t col1 = selseg.colors[1];
uint32_t col2 = selseg.colors[2];
byte colIn[4] = {R(col0), G(col0), B(col0), W(col0)}; byte colIn[4] = {R(col0), G(col0), B(col0), W(col0)};
byte colInSec[4] = {R(col1), G(col1), B(col1), W(col1)}; byte colInSec[4] = {R(col1), G(col1), B(col1), W(col1)};
byte effectIn = selseg.mode; byte effectIn = selseg.mode;
@ -874,7 +883,9 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
if (pos > 0) { if (pos > 0) {
spcI = std::max(0,getNumVal(&req, pos)); spcI = std::max(0,getNumVal(&req, pos));
} }
strip.setSegment(selectedSeg, startI, stopI, grpI, spcI, UINT16_MAX, startY, stopY); strip.suspend(); // must suspend strip operations before changing geometry
selseg.setGeometry(startI, stopI, grpI, spcI, UINT16_MAX, startY, stopY, selseg.map1D2D);
strip.resume();
pos = req.indexOf(F("RV=")); //Segment reverse pos = req.indexOf(F("RV=")); //Segment reverse
if (pos > 0) selseg.reverse = req.charAt(pos+3) != '0'; if (pos > 0) selseg.reverse = req.charAt(pos+3) != '0';
@ -920,7 +931,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
//set brightness //set brightness
updateVal(req.c_str(), "&A=", &bri); updateVal(req.c_str(), "&A=", &bri);
bool col0Changed = false, col1Changed = false; bool col0Changed = false, col1Changed = false, col2Changed = false;
//set colors //set colors
col0Changed |= updateVal(req.c_str(), "&R=", &colIn[0]); col0Changed |= updateVal(req.c_str(), "&R=", &colIn[0]);
col0Changed |= updateVal(req.c_str(), "&G=", &colIn[1]); col0Changed |= updateVal(req.c_str(), "&G=", &colIn[1]);
@ -977,23 +988,23 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
} }
//set color from HEX or 32bit DEC //set color from HEX or 32bit DEC
byte tmpCol[4];
pos = req.indexOf(F("CL=")); pos = req.indexOf(F("CL="));
if (pos > 0) { if (pos > 0) {
colorFromDecOrHexString(colIn, (char*)req.substring(pos + 3).c_str()); colorFromDecOrHexString(colIn, req.substring(pos + 3).c_str());
col0Changed = true; col0Changed = true;
} }
pos = req.indexOf(F("C2=")); pos = req.indexOf(F("C2="));
if (pos > 0) { if (pos > 0) {
colorFromDecOrHexString(colInSec, (char*)req.substring(pos + 3).c_str()); colorFromDecOrHexString(colInSec, req.substring(pos + 3).c_str());
col1Changed = true; col1Changed = true;
} }
pos = req.indexOf(F("C3=")); pos = req.indexOf(F("C3="));
if (pos > 0) { if (pos > 0) {
colorFromDecOrHexString(tmpCol, (char*)req.substring(pos + 3).c_str()); byte tmpCol[4];
uint32_t col2 = RGBW32(tmpCol[0], tmpCol[1], tmpCol[2], tmpCol[3]); 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) selseg.setColor(2, col2); // defined above (SS= or main)
if (!singleSegment) strip.setColor(2, col2); // will set color to all active & selected segments col2Changed = true;
} }
//set to random hue SR=0->1st SR=1->2nd //set to random hue SR=0->1st SR=1->2nd
@ -1004,29 +1015,22 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
col0Changed |= (!sec); col1Changed |= sec; col0Changed |= (!sec); col1Changed |= sec;
} }
//swap 2nd & 1st
pos = req.indexOf(F("SC"));
if (pos > 0) {
byte temp;
for (unsigned i=0; i<4; i++) {
temp = colIn[i];
colIn[i] = colInSec[i];
colInSec[i] = temp;
}
col0Changed = col1Changed = true;
}
// apply colors to selected segment, and all selected segments if applicable // apply colors to selected segment, and all selected segments if applicable
if (col0Changed) { if (col0Changed) {
uint32_t colIn0 = RGBW32(colIn[0], colIn[1], colIn[2], colIn[3]); col0 = RGBW32(colIn[0], colIn[1], colIn[2], colIn[3]);
selseg.setColor(0, colIn0); selseg.setColor(0, col0);
if (!singleSegment) strip.setColor(0, colIn0); // will set color to all active & selected segments
} }
if (col1Changed) { if (col1Changed) {
uint32_t colIn1 = RGBW32(colInSec[0], colInSec[1], colInSec[2], colInSec[3]); col1 = RGBW32(colInSec[0], colInSec[1], colInSec[2], colInSec[3]);
selseg.setColor(1, colIn1); selseg.setColor(1, col1);
if (!singleSegment) strip.setColor(1, colIn1); // will set color to all active & selected segments }
//swap 2nd & 1st
pos = req.indexOf(F("SC"));
if (pos > 0) {
std::swap(col0,col1);
col0Changed = col1Changed = true;
} }
bool fxModeChanged = false, speedChanged = false, intensityChanged = false, paletteChanged = false; bool fxModeChanged = false, speedChanged = false, intensityChanged = false, paletteChanged = false;
@ -1056,6 +1060,9 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
if (speedChanged) seg.speed = speedIn; if (speedChanged) seg.speed = speedIn;
if (intensityChanged) seg.intensity = intensityIn; if (intensityChanged) seg.intensity = intensityIn;
if (paletteChanged) seg.setPalette(paletteIn); if (paletteChanged) seg.setPalette(paletteIn);
if (col0Changed) seg.setColor(0, col0);
if (col1Changed) seg.setColor(1, col1);
if (col2Changed) seg.setColor(2, col2);
if (custom1Changed) seg.custom1 = custom1In; if (custom1Changed) seg.custom1 = custom1In;
if (custom2Changed) seg.custom2 = custom2In; if (custom2Changed) seg.custom2 = custom2In;
if (custom3Changed) seg.custom3 = custom3In; if (custom3Changed) seg.custom3 = custom3In;
@ -1141,7 +1148,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
pos = req.indexOf(F("TT=")); pos = req.indexOf(F("TT="));
if (pos > 0) transitionDelay = getNumVal(&req, pos); if (pos > 0) transitionDelay = getNumVal(&req, pos);
if (fadeTransition) strip.setTransition(transitionDelay); strip.setTransition(transitionDelay);
//set time (unix timestamp) //set time (unix timestamp)
pos = req.indexOf(F("ST=")); pos = req.indexOf(F("ST="));
@ -1191,7 +1198,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
// internal call, does not send XML response // internal call, does not send XML response
pos = req.indexOf(F("IN")); pos = req.indexOf(F("IN"));
if (pos < 1) { if ((request != nullptr) && (pos < 1)) {
auto response = request->beginResponseStream("text/xml"); auto response = request->beginResponseStream("text/xml");
XML_response(*response); XML_response(*response);
request->send(response); request->send(response);

View File

@ -34,8 +34,8 @@ static const int enablePin = -1; // disable the enable pin because it is not ne
static const int rxPin = -1; // disable the receiving pin because it is not needed - softhack007: Pin=-1 means "use default" not "disable" static const int rxPin = -1; // disable the receiving pin because it is not needed - softhack007: Pin=-1 means "use default" not "disable"
static const int txPin = 2; // transmit DMX data over this pin (default is pin 2) static const int txPin = 2; // transmit DMX data over this pin (default is pin 2)
//DMX value array and size. Entry 0 will hold startbyte //DMX value array and size. Entry 0 will hold startbyte, so we need 512+1 elements
static uint8_t dmxData[dmxMaxChannel] = { 0 }; static uint8_t dmxData[dmxMaxChannel+1] = { 0 };
static int chanSize = 0; static int chanSize = 0;
#if !defined(DMX_SEND_ONLY) #if !defined(DMX_SEND_ONLY)
static int currentChannel = 0; static int currentChannel = 0;

View File

@ -206,7 +206,7 @@ void notify(byte callMode, bool followUp)
notificationCount = followUp ? notificationCount + 1 : 0; 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 //ignore notification if received within a second after sending a notification ourselves
if (millis() - notificationSentTime < 1000) return; if (millis() - notificationSentTime < 1000) return;
if (udpIn[1] > 199) return; //do not receive custom versions if (udpIn[1] > 199) return; //do not receive custom versions
@ -225,21 +225,19 @@ void parseNotifyPacket(uint8_t *udpIn) {
// set transition time before making any segment changes // set transition time before making any segment changes
if (version > 3) { if (version > 3) {
if (fadeTransition) {
jsonTransitionOnce = true; jsonTransitionOnce = true;
strip.setTransition(((udpIn[17] << 0) & 0xFF) + ((udpIn[18] << 8) & 0xFF00)); strip.setTransition(((udpIn[17] << 0) & 0xFF) + ((udpIn[18] << 8) & 0xFF00));
} }
}
//apply colors from notification to main segment, only if not syncing full segments //apply colors from notification to main segment, only if not syncing full segments
if ((receiveNotificationColor || !someSel) && (version < 11 || !receiveSegmentOptions)) { if ((receiveNotificationColor || !someSel) && (version < 11 || !receiveSegmentOptions)) {
// primary color, only apply white if intented (version > 0) // primary color, only apply white if intented (version > 0)
strip.setColor(0, RGBW32(udpIn[3], udpIn[4], udpIn[5], (version > 0) ? udpIn[10] : 0)); strip.getMainSegment().setColor(0, RGBW32(udpIn[3], udpIn[4], udpIn[5], (version > 0) ? udpIn[10] : 0));
if (version > 1) { if (version > 1) {
strip.setColor(1, RGBW32(udpIn[12], udpIn[13], udpIn[14], udpIn[15])); // secondary color strip.getMainSegment().setColor(1, RGBW32(udpIn[12], udpIn[13], udpIn[14], udpIn[15])); // secondary color
} }
if (version > 6) { if (version > 6) {
strip.setColor(2, RGBW32(udpIn[20], udpIn[21], udpIn[22], udpIn[23])); // tertiary color strip.getMainSegment().setColor(2, RGBW32(udpIn[20], udpIn[21], udpIn[22], udpIn[23])); // tertiary color
if (version > 9 && udpIn[37] < 255) { // valid CCT/Kelvin value if (version > 9 && udpIn[37] < 255) { // valid CCT/Kelvin value
unsigned cct = udpIn[38]; unsigned cct = udpIn[38];
if (udpIn[37] > 0) { //Kelvin if (udpIn[37] > 0) { //Kelvin
@ -260,11 +258,12 @@ void parseNotifyPacket(uint8_t *udpIn) {
// are we syncing bounds and slave has more active segments than master? // are we syncing bounds and slave has more active segments than master?
if (receiveSegmentBounds && numSrcSegs < strip.getActiveSegmentsNum()) { if (receiveSegmentBounds && numSrcSegs < strip.getActiveSegmentsNum()) {
DEBUG_PRINTLN(F("Removing excessive segments.")); DEBUG_PRINTLN(F("Removing excessive segments."));
for (size_t i=strip.getSegmentsNum(); i>numSrcSegs; i--) { strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case"
if (strip.getSegment(i).isActive()) { for (size_t i=strip.getSegmentsNum(); i>numSrcSegs && i>0; i--) {
strip.setSegment(i-1,0,0); // delete segment Segment &seg = strip.getSegment(i-1);
} if (seg.isActive()) seg.deactivate(); // delete segment
} }
strip.resume();
} }
size_t inactiveSegs = 0; size_t inactiveSegs = 0;
for (size_t i = 0; i < numSrcSegs && i < strip.getMaxSegments(); i++) { for (size_t i = 0; i < numSrcSegs && i < strip.getMaxSegments(); i++) {
@ -300,7 +299,7 @@ void parseNotifyPacket(uint8_t *udpIn) {
if (!receiveSegmentOptions) { if (!receiveSegmentOptions) {
DEBUG_PRINTF_P(PSTR("Set segment w/o options: %d [%d,%d;%d,%d]\n"), id, (int)start, (int)stop, (int)startY, (int)stopY); DEBUG_PRINTF_P(PSTR("Set segment w/o options: %d [%d,%d;%d,%d]\n"), id, (int)start, (int)stop, (int)startY, (int)stopY);
strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case" strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case"
selseg.setUp(start, stop, selseg.grouping, selseg.spacing, offset, startY, stopY); selseg.setGeometry(start, stop, selseg.grouping, selseg.spacing, offset, startY, stopY, selseg.map1D2D);
strip.resume(); strip.resume();
continue; // we do receive bounds, but not options continue; // we do receive bounds, but not options
} }
@ -342,12 +341,12 @@ void parseNotifyPacket(uint8_t *udpIn) {
if (receiveSegmentBounds) { if (receiveSegmentBounds) {
DEBUG_PRINTF_P(PSTR("Set segment w/ options: %d [%d,%d;%d,%d]\n"), id, (int)start, (int)stop, (int)startY, (int)stopY); DEBUG_PRINTF_P(PSTR("Set segment w/ options: %d [%d,%d;%d,%d]\n"), id, (int)start, (int)stop, (int)startY, (int)stopY);
strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case" strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case"
selseg.setUp(start, stop, udpIn[5+ofs], udpIn[6+ofs], offset, startY, stopY); selseg.setGeometry(start, stop, udpIn[5+ofs], udpIn[6+ofs], offset, startY, stopY, selseg.map1D2D);
strip.resume(); strip.resume();
} else { } else {
DEBUG_PRINTF_P(PSTR("Set segment grouping: %d [%d,%d]\n"), id, (int)udpIn[5+ofs], (int)udpIn[6+ofs]); DEBUG_PRINTF_P(PSTR("Set segment grouping: %d [%d,%d]\n"), id, (int)udpIn[5+ofs], (int)udpIn[6+ofs]);
strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case" strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case"
selseg.setUp(selseg.start, selseg.stop, udpIn[5+ofs], udpIn[6+ofs], selseg.offset, selseg.startY, selseg.stopY); selseg.setGeometry(selseg.start, selseg.stop, udpIn[5+ofs], udpIn[6+ofs], selseg.offset, selseg.startY, selseg.stopY, selseg.map1D2D);
strip.resume(); strip.resume();
} }
} }
@ -416,18 +415,18 @@ void realtimeLock(uint32_t timeoutMs, byte md)
start = mainseg.start; start = mainseg.start;
stop = mainseg.stop; stop = mainseg.stop;
mainseg.freeze = true; mainseg.freeze = true;
// if WLED was off and using main segment only, freeze non-main segments so they stay off
if (bri == 0) {
for (size_t s = 0; s < strip.getSegmentsNum(); s++) {
strip.getSegment(s).freeze = true;
}
}
} else { } else {
start = 0; start = 0;
stop = strip.getLengthTotal(); stop = strip.getLengthTotal();
} }
// clear strip/segment // clear strip/segment
for (size_t i = start; i < stop; i++) strip.setPixelColor(i,BLACK); for (size_t i = start; i < stop; i++) strip.setPixelColor(i,BLACK);
// if WLED was off and using main segment only, freeze non-main segments so they stay off
if (useMainSegmentOnly && bri == 0) {
for (size_t s=0; s < strip.getSegmentsNum(); s++) {
strip.getSegment(s).freeze = true;
}
}
} }
// if strip is off (bri==0) and not already in RTM // if strip is off (bri==0) and not already in RTM
if (briT == 0 && !realtimeMode && !realtimeOverride) { if (briT == 0 && !realtimeMode && !realtimeOverride) {
@ -510,12 +509,10 @@ void handleNotifications()
rgbUdp.read(lbuf, packetSize); rgbUdp.read(lbuf, packetSize);
realtimeLock(realtimeTimeoutMs, REALTIME_MODE_HYPERION); realtimeLock(realtimeTimeoutMs, REALTIME_MODE_HYPERION);
if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return; if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return;
unsigned id = 0;
unsigned totalLen = strip.getLengthTotal(); unsigned totalLen = strip.getLengthTotal();
for (size_t i = 0; i < packetSize -2; i += 3) if (useMainSegmentOnly) strip.getMainSegment().beginDraw(); // set up parameters for get/setPixelColor()
{ for (size_t i = 0, id = 0; i < packetSize -2 && id < totalLen; i += 3, id++) {
setRealtimePixel(id, lbuf[i], lbuf[i+1], lbuf[i+2], 0); setRealtimePixel(id, lbuf[i], lbuf[i+1], lbuf[i+2], 0);
id++; if (id >= totalLen) break;
} }
if (!(realtimeMode && useMainSegmentOnly)) strip.show(); if (!(realtimeMode && useMainSegmentOnly)) strip.show();
return; return;
@ -595,17 +592,11 @@ void handleNotifications()
unsigned id = (tpmPayloadFrameSize/3)*(packetNum-1); //start LED unsigned id = (tpmPayloadFrameSize/3)*(packetNum-1); //start LED
unsigned totalLen = strip.getLengthTotal(); unsigned totalLen = strip.getLengthTotal();
for (size_t i = 6; i < tpmPayloadFrameSize + 4U; i += 3) if (useMainSegmentOnly) strip.getMainSegment().beginDraw(); // set up parameters for get/setPixelColor()
{ for (size_t i = 6; i < tpmPayloadFrameSize + 4U && id < totalLen; i += 3, id++) {
if (id < totalLen)
{
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0); setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
id++;
} }
else break; if (tpmPacketCount == numPackets) { //reset packet count and show if all packets were received
}
if (tpmPacketCount == numPackets) //reset packet count and show if all packets were received
{
tpmPacketCount = 0; tpmPacketCount = 0;
strip.show(); strip.show();
} }
@ -629,6 +620,7 @@ void handleNotifications()
if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return; if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return;
unsigned totalLen = strip.getLengthTotal(); unsigned totalLen = strip.getLengthTotal();
if (useMainSegmentOnly) strip.getMainSegment().beginDraw(); // set up parameters for get/setPixelColor()
if (udpIn[0] == 1 && packetSize > 5) //warls if (udpIn[0] == 1 && packetSize > 5) //warls
{ {
for (size_t i = 2; i < packetSize -3; i += 4) for (size_t i = 2; i < packetSize -3; i += 4)
@ -637,39 +629,29 @@ void handleNotifications()
} }
} else if (udpIn[0] == 2 && packetSize > 4) //drgb } else if (udpIn[0] == 2 && packetSize > 4) //drgb
{ {
unsigned id = 0; for (size_t i = 2, id = 0; i < packetSize -2 && id < totalLen; i += 3, id++)
for (size_t i = 2; i < packetSize -2; i += 3)
{ {
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0); setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
id++; if (id >= totalLen) break;
} }
} else if (udpIn[0] == 3 && packetSize > 6) //drgbw } else if (udpIn[0] == 3 && packetSize > 6) //drgbw
{ {
unsigned id = 0; for (size_t i = 2, id = 0; i < packetSize -3 && id < totalLen; i += 4, id++)
for (size_t i = 2; i < packetSize -3; i += 4)
{ {
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]); setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]);
id++; if (id >= totalLen) break;
} }
} else if (udpIn[0] == 4 && packetSize > 7) //dnrgb } else if (udpIn[0] == 4 && packetSize > 7) //dnrgb
{ {
unsigned id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00); unsigned id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00);
for (size_t i = 4; i < packetSize -2; i += 3) for (size_t i = 4; i < packetSize -2 && id < totalLen; i += 3, id++)
{ {
if (id >= totalLen) break;
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0); setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
id++;
} }
} else if (udpIn[0] == 5 && packetSize > 8) //dnrgbw } else if (udpIn[0] == 5 && packetSize > 8) //dnrgbw
{ {
unsigned id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00); unsigned id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00);
for (size_t i = 4; i < packetSize -2; i += 4) for (size_t i = 4; i < packetSize -2 && id < totalLen; i += 4, id++)
{ {
if (id >= totalLen) break;
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]); setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]);
id++;
} }
} }
strip.show(); strip.show();
@ -704,11 +686,11 @@ void setRealtimePixel(uint16_t i, byte r, byte g, byte b, byte w)
b = gamma8(b); b = gamma8(b);
w = gamma8(w); w = gamma8(w);
} }
uint32_t col = RGBW32(r,g,b,w);
if (useMainSegmentOnly) { if (useMainSegmentOnly) {
Segment &seg = strip.getMainSegment(); strip.getMainSegment().setPixelColor(pix, col); // this expects that strip.getMainSegment().beginDraw() has been called in handleNotification()
if (pix<seg.length()) seg.setPixelColor(pix, r, g, b, w);
} else { } else {
strip.setPixelColor(pix, r, g, b, w); strip.setPixelColor(pix, col);
} }
} }
} }
@ -826,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 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}; 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 if (!(apActive || interfacesInited) || !client[0] || !length) return 1; // network not initialised or dummy/unset IP address 031522 ajn added check for ap
WiFiUDP ddpUdp; WiFiUDP ddpUdp;
@ -979,7 +961,7 @@ void espNowReceiveCB(uint8_t* address, uint8_t* data, uint8_t len, signed int rs
// handle WiZ Mote data // handle WiZ Mote data
if (data[0] == 0x91 || data[0] == 0x81 || data[0] == 0x80) { if (data[0] == 0x91 || data[0] == 0x81 || data[0] == 0x80) {
handleRemote(data, len); handleWiZdata(data, len);
return; return;
} }

View File

@ -3,6 +3,9 @@
* Registration and management utility for v2 usermods * Registration and management utility for v2 usermods
*/ */
static Usermod* ums[WLED_MAX_USERMODS] = {nullptr};
byte UsermodManager::numMods = 0;
//Usermod Manager internals //Usermod Manager internals
void UsermodManager::setup() { for (unsigned i = 0; i < numMods; i++) ums[i]->setup(); } void UsermodManager::setup() { for (unsigned i = 0; i < numMods; i++) ums[i]->setup(); }
void UsermodManager::connected() { for (unsigned i = 0; i < numMods; i++) ums[i]->connected(); } void UsermodManager::connected() { for (unsigned i = 0; i < numMods; i++) ums[i]->connected(); }
@ -69,8 +72,6 @@ bool UsermodManager::add(Usermod* um)
return true; return true;
} }
Usermod* UsermodManager::ums[WLED_MAX_USERMODS] = {nullptr};
byte UsermodManager::numMods = 0;
/* Usermod v2 interface shim for oappend */ /* Usermod v2 interface shim for oappend */
Print* Usermod::oappend_shim = nullptr; Print* Usermod::oappend_shim = nullptr;

View File

@ -45,7 +45,7 @@
#endif #endif
#ifdef USERMOD_BH1750 #ifdef USERMOD_BH1750
#include "../usermods/BH1750_v2/usermod_BH1750.h" #include "../usermods/BH1750_v2/usermod_bh1750.h"
#endif #endif
// BME280 v2 usermod. Define "USERMOD_BME280" in my_config.h // BME280 v2 usermod. Define "USERMOD_BME280" in my_config.h
@ -242,6 +242,14 @@
#include "../usermods/LD2410_v2/usermod_ld2410.h" #include "../usermods/LD2410_v2/usermod_ld2410.h"
#endif #endif
#ifdef USERMOD_DEEP_SLEEP
#include "../usermods/deep_sleep/usermod_deep_sleep.h"
#endif
#ifdef USERMOD_RF433
#include "../usermods/usermod_v2_RF433/usermod_v2_RF433.h"
#endif
void registerUsermods() void registerUsermods()
{ {
/* /*
@ -470,4 +478,12 @@ void registerUsermods()
#ifdef USERMOD_POV_DISPLAY #ifdef USERMOD_POV_DISPLAY
UsermodManager::add(new PovDisplayUsermod()); UsermodManager::add(new PovDisplayUsermod());
#endif #endif
#ifdef USERMOD_DEEP_SLEEP
UsermodManager::add(new DeepSleepUsermod());
#endif
#ifdef USERMOD_RF433
UsermodManager::add(new RF433Usermod());
#endif
} }

View File

@ -14,7 +14,7 @@ int getNumVal(const String* req, uint16_t pos)
void parseNumber(const char* str, byte* val, byte minv, byte maxv) void parseNumber(const char* str, byte* val, byte minv, byte maxv)
{ {
if (str == nullptr || str[0] == '\0') return; if (str == nullptr || str[0] == '\0') return;
if (str[0] == 'r') {*val = random8(minv,maxv?maxv:255); return;} // maxv for random cannot be 0 if (str[0] == 'r') {*val = hw_random8(minv,maxv?maxv:255); return;} // maxv for random cannot be 0
bool wrap = false; bool wrap = false;
if (str[0] == 'w' && strlen(str) > 1) {str++; wrap = true;} if (str[0] == 'w' && strlen(str) > 1) {str++; wrap = true;}
if (str[0] == '~') { if (str[0] == '~') {
@ -52,7 +52,7 @@ void parseNumber(const char* str, byte* val, byte minv, byte maxv)
*val = atoi(str); *val = atoi(str);
} }
//getVal supports inc/decrementing and random ("X~Y(r|~[w][-][Z])" form)
bool getVal(JsonVariant elem, byte* val, byte vmin, byte vmax) { bool getVal(JsonVariant elem, byte* val, byte vmin, byte vmax) {
if (elem.is<int>()) { if (elem.is<int>()) {
if (elem < 0) return false; //ignore e.g. {"ps":-1} if (elem < 0) return false; //ignore e.g. {"ps":-1}
@ -60,8 +60,12 @@ bool getVal(JsonVariant elem, byte* val, byte vmin, byte vmax) {
return true; return true;
} else if (elem.is<const char*>()) { } else if (elem.is<const char*>()) {
const char* str = elem; const char* str = elem;
size_t len = strnlen(str, 12); size_t len = strnlen(str, 14);
if (len == 0 || len > 10) return false; if (len == 0 || len > 12) return false;
// fix for #3605 & #4346
// ignore vmin and vmax and use as specified in API
if (len > 3 && (strchr(str,'r') || strchr(str,'~') != strrchr(str,'~'))) vmax = vmin = 0; // we have "X~Y(r|~[w][-][Z])" form
// end fix
parseNumber(str, val, vmin, vmax); parseNumber(str, val, vmin, vmax);
return true; return true;
} }
@ -69,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') { if (elem.is<const char*>() && elem.as<const char*>()[0] == 't') {
return !dflt; return !dflt;
} else { } else {
@ -147,7 +151,7 @@ bool isAsterisksOnly(const char* str, byte maxLen)
//threading/network callback details: https://github.com/Aircoookie/WLED/pull/2336#discussion_r762276994 //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) { if (pDoc == nullptr) {
DEBUG_PRINTLN(F("ERROR: JSON buffer not allocated!")); DEBUG_PRINTLN(F("ERROR: JSON buffer not allocated!"));
@ -171,14 +175,14 @@ bool requestJSONBufferLock(uint8_t module)
#endif #endif
// If the lock is still held - by us, or by another task // If the lock is still held - by us, or by another task
if (jsonBufferLock) { 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 #ifdef ARDUINO_ARCH_ESP32
xSemaphoreGiveRecursive(jsonBufferLockMutex); xSemaphoreGiveRecursive(jsonBufferLockMutex);
#endif #endif
return false; return false;
} }
jsonBufferLock = module ? module : 255; jsonBufferLock = moduleID ? moduleID : 255;
DEBUG_PRINTF_P(PSTR("JSON buffer locked. (%d)\n"), jsonBufferLock); DEBUG_PRINTF_P(PSTR("JSON buffer locked. (%d)\n"), jsonBufferLock);
pDoc->clear(); pDoc->clear();
return true; return true;
@ -261,16 +265,16 @@ uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxL
if (mode < strip.getModeCount()) { if (mode < strip.getModeCount()) {
String lineBuffer = FPSTR(strip.getModeData(mode)); String lineBuffer = FPSTR(strip.getModeData(mode));
if (lineBuffer.length() > 0) { if (lineBuffer.length() > 0) {
unsigned start = lineBuffer.indexOf('@'); int start = lineBuffer.indexOf('@'); // String::indexOf() returns an int, not an unsigned; -1 means "not found"
unsigned stop = lineBuffer.indexOf(';', start); int stop = lineBuffer.indexOf(';', start);
if (start>0 && stop>0) { if (start>0 && stop>0) {
String names = lineBuffer.substring(start, stop); // include @ String names = lineBuffer.substring(start, stop); // include @
unsigned nameBegin = 1, nameEnd, nameDefault; int nameBegin = 1, nameEnd, nameDefault;
if (slider < 10) { if (slider < 10) {
for (size_t i=0; i<=slider; i++) { for (size_t i=0; i<=slider; i++) {
const char *tmpstr; const char *tmpstr;
dest[0] = '\0'; //clear dest buffer 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); nameEnd = names.indexOf(',', nameBegin);
if (i == slider) { if (i == slider) {
nameDefault = names.indexOf('=', nameBegin); // find default value nameDefault = names.indexOf('=', nameBegin); // find default value
@ -372,6 +376,39 @@ uint16_t crc16(const unsigned char* data_p, size_t length) {
return crc; return crc;
} }
// fastled beatsin: 1:1 replacements to remove the use of fastled sin16()
// Generates a 16-bit sine wave at a given BPM that oscillates within a given range. see fastled for details.
uint16_t beatsin88_t(accum88 beats_per_minute_88, uint16_t lowest, uint16_t highest, uint32_t timebase, uint16_t phase_offset)
{
uint16_t beat = beat88( beats_per_minute_88, timebase);
uint16_t beatsin (sin16_t( beat + phase_offset) + 32768);
uint16_t rangewidth = highest - lowest;
uint16_t scaledbeat = scale16( beatsin, rangewidth);
uint16_t result = lowest + scaledbeat;
return result;
}
// Generates a 16-bit sine wave at a given BPM that oscillates within a given range. see fastled for details.
uint16_t beatsin16_t(accum88 beats_per_minute, uint16_t lowest, uint16_t highest, uint32_t timebase, uint16_t phase_offset)
{
uint16_t beat = beat16( beats_per_minute, timebase);
uint16_t beatsin = (sin16_t( beat + phase_offset) + 32768);
uint16_t rangewidth = highest - lowest;
uint16_t scaledbeat = scale16( beatsin, rangewidth);
uint16_t result = lowest + scaledbeat;
return result;
}
// Generates an 8-bit sine wave at a given BPM that oscillates within a given range. see fastled for details.
uint8_t beatsin8_t(accum88 beats_per_minute, uint8_t lowest, uint8_t highest, uint32_t timebase, uint8_t phase_offset)
{
uint8_t beat = beat8( beats_per_minute, timebase);
uint8_t beatsin = sin8_t( beat + phase_offset);
uint8_t rangewidth = highest - lowest;
uint8_t scaledbeat = scale8( beatsin, rangewidth);
uint8_t result = lowest + scaledbeat;
return result;
}
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// Begin simulateSound (to enable audio enhanced effects to display something) // Begin simulateSound (to enable audio enhanced effects to display something)
@ -431,15 +468,15 @@ um_data_t* simulateSound(uint8_t simulationId)
default: default:
case UMS_BeatSin: case UMS_BeatSin:
for (int i = 0; i<16; i++) for (int i = 0; i<16; i++)
fftResult[i] = beatsin8(120 / (i+1), 0, 255); fftResult[i] = beatsin8_t(120 / (i+1), 0, 255);
// fftResult[i] = (beatsin8(120, 0, 255) + (256/16 * i)) % 256; // fftResult[i] = (beatsin8_t(120, 0, 255) + (256/16 * i)) % 256;
volumeSmth = fftResult[8]; volumeSmth = fftResult[8];
break; break;
case UMS_WeWillRockYou: case UMS_WeWillRockYou:
if (ms%2000 < 200) { if (ms%2000 < 200) {
volumeSmth = random8(255); volumeSmth = hw_random8();
for (int i = 0; i<5; i++) for (int i = 0; i<5; i++)
fftResult[i] = random8(255); fftResult[i] = hw_random8();
} }
else if (ms%2000 < 400) { else if (ms%2000 < 400) {
volumeSmth = 0; volumeSmth = 0;
@ -447,9 +484,9 @@ um_data_t* simulateSound(uint8_t simulationId)
fftResult[i] = 0; fftResult[i] = 0;
} }
else if (ms%2000 < 600) { else if (ms%2000 < 600) {
volumeSmth = random8(255); volumeSmth = hw_random8();
for (int i = 5; i<11; i++) for (int i = 5; i<11; i++)
fftResult[i] = random8(255); fftResult[i] = hw_random8();
} }
else if (ms%2000 < 800) { else if (ms%2000 < 800) {
volumeSmth = 0; volumeSmth = 0;
@ -457,9 +494,9 @@ um_data_t* simulateSound(uint8_t simulationId)
fftResult[i] = 0; fftResult[i] = 0;
} }
else if (ms%2000 < 1000) { else if (ms%2000 < 1000) {
volumeSmth = random8(255); volumeSmth = hw_random8();
for (int i = 11; i<16; i++) for (int i = 11; i<16; i++)
fftResult[i] = random8(255); fftResult[i] = hw_random8();
} }
else { else {
volumeSmth = 0; volumeSmth = 0;
@ -469,17 +506,17 @@ um_data_t* simulateSound(uint8_t simulationId)
break; break;
case UMS_10_13: case UMS_10_13:
for (int i = 0; i<16; i++) for (int i = 0; i<16; i++)
fftResult[i] = inoise8(beatsin8(90 / (i+1), 0, 200)*15 + (ms>>10), ms>>3); fftResult[i] = inoise8(beatsin8_t(90 / (i+1), 0, 200)*15 + (ms>>10), ms>>3);
volumeSmth = fftResult[8]; volumeSmth = fftResult[8];
break; break;
case UMS_14_3: case UMS_14_3:
for (int i = 0; i<16; i++) for (int i = 0; i<16; i++)
fftResult[i] = inoise8(beatsin8(120 / (i+1), 10, 30)*10 + (ms>>14), ms>>3); fftResult[i] = inoise8(beatsin8_t(120 / (i+1), 10, 30)*10 + (ms>>14), ms>>3);
volumeSmth = fftResult[8]; volumeSmth = fftResult[8];
break; break;
} }
samplePeak = random8() > 250; samplePeak = hw_random8() > 250;
FFT_MajorPeak = 21 + (volumeSmth*volumeSmth) / 8.0f; // walk thru full range of 21hz...8200hz FFT_MajorPeak = 21 + (volumeSmth*volumeSmth) / 8.0f; // walk thru full range of 21hz...8200hz
maxVol = 31; // this gets feedback fro UI maxVol = 31; // this gets feedback fro UI
binNum = 8; // this gets feedback fro UI binNum = 8; // this gets feedback fro UI
@ -501,7 +538,7 @@ void enumerateLedmaps() {
#ifndef ESP8266 #ifndef ESP8266
if (ledmapNames[i-1]) { //clear old name if (ledmapNames[i-1]) { //clear old name
delete[] ledmapNames[i-1]; free(ledmapNames[i-1]);
ledmapNames[i-1] = nullptr; ledmapNames[i-1] = nullptr;
} }
#endif #endif
@ -519,7 +556,7 @@ void enumerateLedmaps() {
const char *name = root["n"].as<const char*>(); const char *name = root["n"].as<const char*>();
if (name != nullptr) len = strlen(name); if (name != nullptr) len = strlen(name);
if (len > 0 && len < 33) { 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); if (ledmapNames[i-1]) strlcpy(ledmapNames[i-1], name, 33);
} }
} }
@ -527,7 +564,7 @@ void enumerateLedmaps() {
char tmp[33]; char tmp[33];
snprintf_P(tmp, 32, s_ledmap_tmpl, i); snprintf_P(tmp, 32, s_ledmap_tmpl, i);
len = strlen(tmp); 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); if (ledmapNames[i-1]) strlcpy(ledmapNames[i-1], tmp, 33);
} }
} }
@ -545,7 +582,7 @@ void enumerateLedmaps() {
uint8_t get_random_wheel_index(uint8_t pos) { uint8_t get_random_wheel_index(uint8_t pos) {
uint8_t r = 0, x = 0, y = 0, d = 0; uint8_t r = 0, x = 0, y = 0, d = 0;
while (d < 42) { while (d < 42) {
r = random8(); r = hw_random8();
x = abs(pos - r); x = abs(pos - r);
y = 255 - x; y = 255 - x;
d = MIN(x, y); d = MIN(x, y);
@ -557,3 +594,25 @@ 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) { 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; 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();
uint64_t scaled = uint64_t(rnd) * uint64_t(upperlimit);
return scaled >> 32;
}
int32_t hw_random(int32_t lowerlimit, int32_t upperlimit) {
if(lowerlimit >= upperlimit) {
return lowerlimit;
}
uint32_t diff = upperlimit - lowerlimit;
return hw_random(diff) + lowerlimit;
}

View File

@ -65,7 +65,10 @@ void WLED::loop()
handleNotifications(); handleNotifications();
handleTransitions(); handleTransitions();
#ifdef WLED_ENABLE_DMX #ifdef WLED_ENABLE_DMX
handleDMX(); handleDMXOutput();
#endif
#ifdef WLED_ENABLE_DMX_INPUT
dmxInput.update();
#endif #endif
#ifdef WLED_DEBUG #ifdef WLED_DEBUG
@ -84,6 +87,9 @@ void WLED::loop()
#ifndef WLED_DISABLE_INFRARED #ifndef WLED_DISABLE_INFRARED
handleIR(); handleIR();
#endif #endif
#ifndef WLED_DISABLE_ESPNOW
handleRemote();
#endif
#ifndef WLED_DISABLE_ALEXA #ifndef WLED_DISABLE_ALEXA
handleAlexa(); handleAlexa();
#endif #endif
@ -222,6 +228,7 @@ void WLED::loop()
BusManager::setBrightness(bri); // fix re-initialised bus' brightness #4005 BusManager::setBrightness(bri); // fix re-initialised bus' brightness #4005
if (aligned) strip.makeAutoSegments(); if (aligned) strip.makeAutoSegments();
else strip.fixInvalidSegments(); else strip.fixInvalidSegments();
BusManager::setBrightness(bri); // fix re-initialised bus' brightness
doSerializeConfig = true; doSerializeConfig = true;
} }
if (loadLedmap >= 0) { if (loadLedmap >= 0) {
@ -296,6 +303,7 @@ void WLED::loop()
DEBUG_PRINTF_P(PSTR("Strip time[ms]:%u/%lu\n"), avgStripMillis/loops, maxStripMillis); DEBUG_PRINTF_P(PSTR("Strip time[ms]:%u/%lu\n"), avgStripMillis/loops, maxStripMillis);
} }
strip.printSize(); strip.printSize();
server.printStatus(DEBUGOUT);
loops = 0; loops = 0;
maxLoopMillis = 0; maxLoopMillis = 0;
maxUsermodMillis = 0; maxUsermodMillis = 0;
@ -478,10 +486,7 @@ void WLED::setup()
if (strcmp(multiWiFi[0].clientSSID, DEFAULT_CLIENT_SSID) == 0) if (strcmp(multiWiFi[0].clientSSID, DEFAULT_CLIENT_SSID) == 0)
showWelcomePage = true; showWelcomePage = true;
WiFi.persistent(false); WiFi.persistent(false);
#ifdef WLED_USE_ETHERNET
WiFi.onEvent(WiFiEvent); WiFi.onEvent(WiFiEvent);
#endif
WiFi.mode(WIFI_STA); // enable scanning WiFi.mode(WIFI_STA); // enable scanning
findWiFi(true); // start scanning for available WiFi-s findWiFi(true); // start scanning for available WiFi-s
@ -526,7 +531,10 @@ void WLED::setup()
} }
#endif #endif
#ifdef WLED_ENABLE_DMX #ifdef WLED_ENABLE_DMX
initDMX(); initDMXOutput();
#endif
#ifdef WLED_ENABLE_DMX_INPUT
dmxInput.init(dmxInputReceivePin, dmxInputTransmitPin, dmxInputEnablePin, dmxInputPort);
#endif #endif
#ifdef WLED_ENABLE_ADALIGHT #ifdef WLED_ENABLE_ADALIGHT
@ -546,14 +554,8 @@ void WLED::setup()
#endif #endif
// Seed FastLED random functions with an esp random value, which already works properly at this point. // Seed FastLED random functions with an esp random value, which already works properly at this point.
#if defined(ARDUINO_ARCH_ESP32) const uint32_t seed32 = hw_random();
const uint32_t seed32 = esp_random(); random16_set_seed((uint16_t)seed32);
#elif defined(ARDUINO_ARCH_ESP8266)
const uint32_t seed32 = RANDOM_REG32;
#else
const uint32_t seed32 = random(std::numeric_limits<long>::max());
#endif
random16_set_seed((uint16_t)((seed32 & 0xFFFF) ^ (seed32 >> 16)));
#if WLED_WATCHDOG_TIMEOUT > 0 #if WLED_WATCHDOG_TIMEOUT > 0
enableWatchdog(); enableWatchdog();
@ -578,10 +580,11 @@ void WLED::beginStrip()
} else { } else {
// fix for #3196 // fix for #3196
if (bootPreset > 0) { if (bootPreset > 0) {
bool oldTransition = fadeTransition; // workaround if transitions are enabled // set all segments black (no transition)
fadeTransition = false; // ignore transitions temporarily for (unsigned i = 0; i < strip.getSegmentsNum(); i++) {
strip.setColor(0, BLACK); // set all segments black Segment &seg = strip.getSegment(i);
fadeTransition = oldTransition; // restore transitions if (seg.isActive()) seg.colors[0] = BLACK;
}
col[0] = col[1] = col[2] = col[3] = 0; // needed for colorUpdated() col[0] = col[1] = col[2] = col[3] = 0; // needed for colorUpdated()
} }
briLast = briS; bri = 0; briLast = briS; bri = 0;
@ -781,8 +784,7 @@ int8_t WLED::findWiFi(bool doScan) {
void WLED::initConnection() void WLED::initConnection()
{ {
DEBUG_PRINTLN(F("initConnection() called.")); DEBUG_PRINTF_P(PSTR("initConnection() called @ %lus.\n"), millis()/1000);
#ifdef WLED_ENABLE_WEBSOCKETS #ifdef WLED_ENABLE_WEBSOCKETS
ws.onEvent(wsEvent); ws.onEvent(wsEvent);
#endif #endif
@ -811,6 +813,7 @@ void WLED::initConnection()
if (!WLED_WIFI_CONFIGURED) { if (!WLED_WIFI_CONFIGURED) {
DEBUG_PRINTLN(F("No connection configured.")); DEBUG_PRINTLN(F("No connection configured."));
if (!apActive) initAP(); // instantly go to ap mode if (!apActive) initAP(); // instantly go to ap mode
return;
} else if (!apActive) { } else if (!apActive) {
if (apBehavior == AP_BEHAVIOR_ALWAYS) { if (apBehavior == AP_BEHAVIOR_ALWAYS) {
DEBUG_PRINTLN(F("Access point ALWAYS enabled.")); DEBUG_PRINTLN(F("Access point ALWAYS enabled."));
@ -825,9 +828,7 @@ void WLED::initConnection()
if (WLED_WIFI_CONFIGURED) { if (WLED_WIFI_CONFIGURED) {
showWelcomePage = false; showWelcomePage = false;
DEBUG_PRINT(F("Connecting to ")); DEBUG_PRINTF_P(PSTR("Connecting to %s...\n"), multiWiFi[selectedWiFi].clientSSID);
DEBUG_PRINT(multiWiFi[selectedWiFi].clientSSID);
DEBUG_PRINTLN(F("..."));
// convert the "serverDescription" into a valid DNS hostname (alphanumeric) // convert the "serverDescription" into a valid DNS hostname (alphanumeric)
char hostname[25]; char hostname[25];
@ -926,7 +927,8 @@ void WLED::handleConnection()
{ {
static bool scanDone = true; static bool scanDone = true;
static byte stacO = 0; static byte stacO = 0;
unsigned long now = millis(); const unsigned long now = millis();
const unsigned long nowS = now/1000;
const bool wifiConfigured = WLED_WIFI_CONFIGURED; const bool wifiConfigured = WLED_WIFI_CONFIGURED;
// ignore connection handling if WiFi is configured and scan still running // ignore connection handling if WiFi is configured and scan still running
@ -935,7 +937,7 @@ void WLED::handleConnection()
return; return;
if (lastReconnectAttempt == 0 || forceReconnect) { if (lastReconnectAttempt == 0 || forceReconnect) {
DEBUG_PRINTLN(F("Initial connect or forced reconnect.")); DEBUG_PRINTF_P(PSTR("Initial connect or forced reconnect (@ %lus).\n"), nowS);
selectedWiFi = findWiFi(); // find strongest WiFi selectedWiFi = findWiFi(); // find strongest WiFi
initConnection(); initConnection();
interfacesInited = false; interfacesInited = false;
@ -955,8 +957,7 @@ void WLED::handleConnection()
#endif #endif
if (stac != stacO) { if (stac != stacO) {
stacO = stac; stacO = stac;
DEBUG_PRINT(F("Connected AP clients: ")); DEBUG_PRINTF_P(PSTR("Connected AP clients: %d\n"), (int)stac);
DEBUG_PRINTLN(stac);
if (!WLED_CONNECTED && wifiConfigured) { // trying to connect, but not connected if (!WLED_CONNECTED && wifiConfigured) { // trying to connect, but not connected
if (stac) if (stac)
WiFi.disconnect(); // disable search so that AP can work WiFi.disconnect(); // disable search so that AP can work
@ -979,6 +980,7 @@ void WLED::handleConnection()
initConnection(); initConnection();
interfacesInited = false; interfacesInited = false;
scanDone = true; scanDone = true;
return;
} }
//send improv failed 6 seconds after second init attempt (24 sec. after provisioning) //send improv failed 6 seconds after second init attempt (24 sec. after provisioning)
if (improvActive > 2 && now - lastReconnectAttempt > 6000) { if (improvActive > 2 && now - lastReconnectAttempt > 6000) {
@ -987,13 +989,13 @@ void WLED::handleConnection()
} }
if (now - lastReconnectAttempt > ((stac) ? 300000 : 18000) && wifiConfigured) { if (now - lastReconnectAttempt > ((stac) ? 300000 : 18000) && wifiConfigured) {
if (improvActive == 2) improvActive = 3; if (improvActive == 2) improvActive = 3;
DEBUG_PRINTLN(F("Last reconnect too old.")); DEBUG_PRINTF_P(PSTR("Last reconnect (%lus) too old (@ %lus).\n"), lastReconnectAttempt/1000, nowS);
if (++selectedWiFi >= multiWiFi.size()) selectedWiFi = 0; // we couldn't connect, try with another network from the list if (++selectedWiFi >= multiWiFi.size()) selectedWiFi = 0; // we couldn't connect, try with another network from the list
initConnection(); initConnection();
} }
if (!apActive && now - lastReconnectAttempt > 12000 && (!wasConnected || apBehavior == AP_BEHAVIOR_NO_CONN)) { if (!apActive && now - lastReconnectAttempt > 12000 && (!wasConnected || apBehavior == AP_BEHAVIOR_NO_CONN)) {
if (!(apBehavior == AP_BEHAVIOR_TEMPORARY && now > WLED_AP_TIMEOUT)) { if (!(apBehavior == AP_BEHAVIOR_TEMPORARY && now > WLED_AP_TIMEOUT)) {
DEBUG_PRINTLN(F("Not connected AP.")); DEBUG_PRINTF_P(PSTR("Not connected AP (@ %lus).\n"), nowS);
initAP(); // start AP only within first 5min initAP(); // start AP only within first 5min
} }
} }
@ -1003,7 +1005,7 @@ void WLED::handleConnection()
dnsServer.stop(); dnsServer.stop();
WiFi.softAPdisconnect(true); WiFi.softAPdisconnect(true);
apActive = false; apActive = false;
DEBUG_PRINTLN(F("Temporary AP disabled.")); DEBUG_PRINTF_P(PSTR("Temporary AP disabled (@ %lus).\n"), nowS);
} }
} }
} else if (!interfacesInited) { //newly connected } else if (!interfacesInited) { //newly connected

View File

@ -3,12 +3,11 @@
/* /*
Main sketch, global variable declarations Main sketch, global variable declarations
@title WLED project sketch @title WLED project sketch
@version 0.15.0-b7
@author Christian Schwinne @author Christian Schwinne
*/ */
// version code in format yymmddb (b = daily build) // version code in format yymmddb (b = daily build)
#define VERSION 2410270 #define VERSION 2412040
//uncomment this if you have a "my_config.h" file you'd like to use //uncomment this if you have a "my_config.h" file you'd like to use
//#define WLED_USE_MY_CONFIG //#define WLED_USE_MY_CONFIG
@ -145,6 +144,10 @@
#endif #endif
#endif #endif
#ifdef WLED_ENABLE_DMX_INPUT
#include "dmx_input.h"
#endif
#include "src/dependencies/e131/ESPAsyncE131.h" #include "src/dependencies/e131/ESPAsyncE131.h"
#ifndef WLED_DISABLE_MQTT #ifndef WLED_DISABLE_MQTT
#include "src/dependencies/async-mqtt-client/AsyncMqttClient.h" #include "src/dependencies/async-mqtt-client/AsyncMqttClient.h"
@ -264,13 +267,13 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument<PSRAM_Allocator>;
#define WLED_VERSION dev #define WLED_VERSION dev
#endif #endif
#ifndef WLED_RELEASE_NAME #ifndef WLED_RELEASE_NAME
#define WLED_RELEASE_NAME dev_release #define WLED_RELEASE_NAME "Custom"
#endif #endif
// Global Variable definitions // Global Variable definitions
WLED_GLOBAL char versionString[] _INIT(TOSTRING(WLED_VERSION)); WLED_GLOBAL char versionString[] _INIT(TOSTRING(WLED_VERSION));
WLED_GLOBAL char releaseString[] _INIT(TOSTRING(WLED_RELEASE_NAME)); // somehow this will not work if using "const char releaseString[] WLED_GLOBAL char releaseString[] _INIT(WLED_RELEASE_NAME); // must include the quotes when defining, e.g -D WLED_RELEASE_NAME=\"ESP32_MULTI_USREMODS\"
#define WLED_CODENAME "Kōsen" #define WLED_CODENAME "Niji"
// AP and OTA default passwords (for maximum security change them!) // AP and OTA default passwords (for maximum security change them!)
WLED_GLOBAL char apPass[65] _INIT(WLED_AP_PASS); WLED_GLOBAL char apPass[65] _INIT(WLED_AP_PASS);
@ -460,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 DMXStart _INIT(10); // start address of the first fixture
WLED_GLOBAL uint16_t DMXStartLED _INIT(0); // LED from which DMX fixtures start WLED_GLOBAL uint16_t DMXStartLED _INIT(0); // LED from which DMX fixtures start
#endif #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 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 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 WLED_GLOBAL E131Priority highPriority _INIT(3); // E1.31 highest priority tracking, init = timeout in seconds
@ -483,10 +494,10 @@ WLED_GLOBAL unsigned long lastMqttReconnectAttempt _INIT(0); // used for other
#endif #endif
WLED_GLOBAL AsyncMqttClient *mqtt _INIT(NULL); WLED_GLOBAL AsyncMqttClient *mqtt _INIT(NULL);
WLED_GLOBAL bool mqttEnabled _INIT(false); WLED_GLOBAL bool mqttEnabled _INIT(false);
WLED_GLOBAL char mqttStatusTopic[40] _INIT(""); // this must be global because of async handlers WLED_GLOBAL char mqttStatusTopic[MQTT_MAX_TOPIC_LEN + 8] _INIT(""); // this must be global because of async handlers
WLED_GLOBAL char mqttDeviceTopic[MQTT_MAX_TOPIC_LEN+1] _INIT(""); // main MQTT topic (individual per device, default is wled/mac) WLED_GLOBAL char mqttDeviceTopic[MQTT_MAX_TOPIC_LEN + 1] _INIT(""); // main MQTT topic (individual per device, default is wled/mac)
WLED_GLOBAL char mqttGroupTopic[MQTT_MAX_TOPIC_LEN+1] _INIT("wled/all"); // second MQTT topic (for example to group devices) WLED_GLOBAL char mqttGroupTopic[MQTT_MAX_TOPIC_LEN + 1] _INIT("wled/all"); // second MQTT topic (for example to group devices)
WLED_GLOBAL char mqttServer[MQTT_MAX_SERVER_LEN+1] _INIT(""); // both domains and IPs should work (no SSL) WLED_GLOBAL char mqttServer[MQTT_MAX_SERVER_LEN + 1] _INIT(""); // both domains and IPs should work (no SSL)
WLED_GLOBAL char mqttUser[41] _INIT(""); // optional: username for MQTT auth WLED_GLOBAL char mqttUser[41] _INIT(""); // optional: username for MQTT auth
WLED_GLOBAL char mqttPass[65] _INIT(""); // optional: password for MQTT auth WLED_GLOBAL char mqttPass[65] _INIT(""); // optional: password for MQTT auth
WLED_GLOBAL char mqttClientID[41] _INIT(""); // override the client ID WLED_GLOBAL char mqttClientID[41] _INIT(""); // override the client ID
@ -577,13 +588,11 @@ 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 WLED_GLOBAL byte lastRandomIndex _INIT(0); // used to save last random color so the new one is not the same
// transitions // transitions
WLED_GLOBAL bool fadeTransition _INIT(true); // enable crossfading brightness/color WLED_GLOBAL uint8_t blendingStyle _INIT(0); // effect blending/transitionig style
WLED_GLOBAL bool modeBlending _INIT(true); // enable effect blending
WLED_GLOBAL bool transitionActive _INIT(false); WLED_GLOBAL bool transitionActive _INIT(false);
WLED_GLOBAL uint16_t transitionDelay _INIT(750); // global transition duration WLED_GLOBAL uint16_t transitionDelay _INIT(750); // global transition duration
WLED_GLOBAL uint16_t transitionDelayDefault _INIT(750); // default transition time (stored in cfg.json) WLED_GLOBAL uint16_t transitionDelayDefault _INIT(750); // default transition time (stored in cfg.json)
WLED_GLOBAL unsigned long transitionStartTime; WLED_GLOBAL unsigned long transitionStartTime;
WLED_GLOBAL float tperLast _INIT(0.0f); // crossfade transition progress, 0.0f - 1.0f
WLED_GLOBAL bool jsonTransitionOnce _INIT(false); // flag to override transitionDelay (playlist, JSON API: "live" & "seg":{"i"} & "tt") WLED_GLOBAL bool jsonTransitionOnce _INIT(false); // flag to override transitionDelay (playlist, JSON API: "live" & "seg":{"i"} & "tt")
WLED_GLOBAL uint8_t randomPaletteChangeTime _INIT(5); // amount of time [s] between random palette changes (min: 1s, max: 255s) WLED_GLOBAL uint8_t randomPaletteChangeTime _INIT(5); // amount of time [s] between random palette changes (min: 1s, max: 255s)
WLED_GLOBAL bool useHarmonicRandomPalette _INIT(true); // use *harmonic* random palette generation (nicer looking) or truly random WLED_GLOBAL bool useHarmonicRandomPalette _INIT(true); // use *harmonic* random palette generation (nicer looking) or truly random
@ -865,7 +874,7 @@ WLED_GLOBAL bool ledStatusState _INIT(false); // the current LED state
#endif #endif
// server library objects // 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 #ifdef WLED_ENABLE_WEBSOCKETS
WLED_GLOBAL AsyncWebSocket ws _INIT_N((("/ws"))); WLED_GLOBAL AsyncWebSocket ws _INIT_N((("/ws")));
#endif #endif
@ -897,9 +906,6 @@ WLED_GLOBAL uint32_t ledMaps _INIT(0); // bitfield representation of available l
WLED_GLOBAL uint16_t ledMaps _INIT(0); // bitfield representation of available ledmaps WLED_GLOBAL uint16_t ledMaps _INIT(0); // bitfield representation of available ledmaps
#endif #endif
// Usermod manager
WLED_GLOBAL UsermodManager usermods _INIT(UsermodManager());
// global I2C SDA pin (used for usermods) // global I2C SDA pin (used for usermods)
#ifndef I2CSDAPIN #ifndef I2CSDAPIN
WLED_GLOBAL int8_t i2c_sda _INIT(-1); WLED_GLOBAL int8_t i2c_sda _INIT(-1);

6
wled00/wled_eeprom.cpp Executable file → Normal file
View File

@ -2,6 +2,10 @@
#include <EEPROM.h> #include <EEPROM.h>
#include "wled.h" #include "wled.h"
#if defined(WLED_ENABLE_MQTT) && MQTT_MAX_TOPIC_LEN < 32
#error "MQTT topics length < 32 is not supported by the EEPROM module!"
#endif
/* /*
* DEPRECATED, do not use for new settings * DEPRECATED, do not use for new settings
* Only used to restore config from pre-0.11 installations using the deEEP() methods * Only used to restore config from pre-0.11 installations using the deEEP() methods
@ -220,7 +224,7 @@ void loadSettingsFromEEPROM()
if (lastEEPROMversion > 7) if (lastEEPROMversion > 7)
{ {
strip.paletteFade = EEPROM.read(374); //strip.paletteFade = EEPROM.read(374);
strip.paletteBlend = EEPROM.read(382); strip.paletteBlend = EEPROM.read(382);
for (int i = 0; i < 8; ++i) for (int i = 0; i < 8; ++i)

View File

@ -10,16 +10,25 @@
//#define WLED_DEBUG_MATH //#define WLED_DEBUG_MATH
// Note: cos_t, sin_t and tan_t are very accurate but slow
// the math.h functions use several kB of flash and are to be avoided if possible
// sin16_t / cos16_t are faster and much more accurate than the fastled variants
// sin_approx and cos_approx are float wrappers for sin16_t/cos16_t and have an accuracy better than +/-0.0015 compared to sinf()
// sin8_t / cos8_t are fastled replacements and use sin16_t / cos16_t. Slightly slower than fastled version but very accurate
// Taylor series approximations, replaced with Bhaskara I's approximation
/*
#define modd(x, y) ((x) - (int)((x) / (y)) * (y)) #define modd(x, y) ((x) - (int)((x) / (y)) * (y))
float cos_t(float phi) float cos_t(float phi)
{ {
float x = modd(phi, TWO_PI); float x = modd(phi, M_TWOPI);
if (x < 0) x = -1 * x; if (x < 0) x = -1 * x;
int8_t sign = 1; int8_t sign = 1;
if (x > PI) if (x > M_PI)
{ {
x -= PI; x -= M_PI;
sign = -1; sign = -1;
} }
float xx = x * x; float xx = x * x;
@ -31,8 +40,8 @@ float cos_t(float phi)
return res; return res;
} }
float sin_t(float x) { float sin_t(float phi) {
float res = cos_t(HALF_PI - x); float res = cos_t(M_PI_2 - phi);
#ifdef WLED_DEBUG_MATH #ifdef WLED_DEBUG_MATH
Serial.printf("sin: %f,%f,%f,(%f)\n",x,res,sin(x),res-sin(x)); Serial.printf("sin: %f,%f,%f,(%f)\n",x,res,sin(x),res-sin(x));
#endif #endif
@ -48,6 +57,80 @@ float tan_t(float x) {
#endif #endif
return res; return res;
} }
*/
// 16-bit, integer based Bhaskara I's sine approximation: 16*x*(pi - x) / (5*pi^2 - 4*x*(pi - x))
// input is 16bit unsigned (0-65535), output is 16bit signed (-32767 to +32767)
// optimized integer implementation by @dedehai
int16_t sin16_t(uint16_t theta) {
int scale = 1;
if (theta > 0x7FFF) {
theta = 0xFFFF - theta;
scale = -1; // second half of the sine function is negative (pi - 2*pi)
}
uint32_t precal = theta * (0x7FFF - theta);
uint64_t numerator = (uint64_t)precal * (4 * 0x7FFF); // 64bit required
int32_t denominator = 1342095361 - precal; // 1342095361 is 5 * 0x7FFF^2 / 4
int16_t result = numerator / denominator;
return result * scale;
}
int16_t cos16_t(uint16_t theta) {
return sin16_t(theta + 0x4000); //cos(x) = sin(x+pi/2)
}
uint8_t sin8_t(uint8_t theta) {
int32_t sin16 = sin16_t((uint16_t)theta * 257); // 255 * 257 = 0xFFFF
sin16 += 0x7FFF + 128; //shift result to range 0-0xFFFF, +128 for rounding
return min(sin16, int32_t(0xFFFF)) >> 8; // min performs saturation, and prevents overflow
}
uint8_t cos8_t(uint8_t theta) {
return sin8_t(theta + 64); //cos(x) = sin(x+pi/2)
}
float sin_approx(float theta) {
uint16_t scaled_theta = (int)(theta * (float)(0xFFFF / M_TWOPI)); // note: do not cast negative float to uint! cast to int first (undefined on C3)
int32_t result = sin16_t(scaled_theta);
float sin = float(result) / 0x7FFF;
return sin;
}
float cos_approx(float theta) {
uint16_t scaled_theta = (int)(theta * (float)(0xFFFF / M_TWOPI)); // note: do not cast negative float to uint! cast to int first (undefined on C3)
int32_t result = sin16_t(scaled_theta + 0x4000);
float cos = float(result) / 0x7FFF;
return cos;
}
float tan_approx(float x) {
float c = cos_approx(x);
if (c==0.0f) return 0;
float res = sin_approx(x) / c;
return res;
}
#define ATAN2_CONST_A 0.1963f
#define ATAN2_CONST_B 0.9817f
// atan2_t approximation, with the idea from https://gist.github.com/volkansalma/2972237?permalink_comment_id=3872525#gistcomment-3872525
float atan2_t(float y, float x) {
float abs_y = fabs(y);
float abs_x = fabs(x);
float r = (abs_x - abs_y) / (abs_y + abs_x + 1e-10f); // avoid division by zero by adding a small nubmer
float angle;
if(x < 0) {
r = -r;
angle = M_PI_2 + M_PI_4;
}
else
angle = M_PI_2 - M_PI_4;
float add = (ATAN2_CONST_A * (r * r) - ATAN2_CONST_B) * r;
angle += add;
angle = y < 0 ? -angle : angle;
return angle;
}
//https://stackoverflow.com/questions/3380628 //https://stackoverflow.com/questions/3380628
// Absolute error <= 6.7e-5 // Absolute error <= 6.7e-5
@ -60,10 +143,10 @@ float acos_t(float x) {
ret = ret * xabs; ret = ret * xabs;
ret = ret - 0.2121144f; ret = ret - 0.2121144f;
ret = ret * xabs; ret = ret * xabs;
ret = ret + HALF_PI; ret = ret + M_PI_2;
ret = ret * sqrt(1.0f-xabs); ret = ret * sqrt(1.0f-xabs);
ret = ret - 2 * negate * ret; ret = ret - 2 * negate * ret;
float res = negate * PI + ret; float res = negate * M_PI + ret;
#ifdef WLED_DEBUG_MATH #ifdef WLED_DEBUG_MATH
Serial.printf("acos: %f,%f,%f,(%f)\n",x,res,acos(x),res-acos(x)); Serial.printf("acos: %f,%f,%f,(%f)\n",x,res,acos(x),res-acos(x));
#endif #endif
@ -71,7 +154,7 @@ float acos_t(float x) {
} }
float asin_t(float x) { float asin_t(float x) {
float res = HALF_PI - acos_t(x); float res = M_PI_2 - acos_t(x);
#ifdef WLED_DEBUG_MATH #ifdef WLED_DEBUG_MATH
Serial.printf("asin: %f,%f,%f,(%f)\n",x,res,asin(x),res-asin(x)); Serial.printf("asin: %f,%f,%f,(%f)\n",x,res,asin(x),res-asin(x));
#endif #endif
@ -87,7 +170,7 @@ float atan_t(float x) {
//For A/B/C, see https://stackoverflow.com/a/42542593 //For A/B/C, see https://stackoverflow.com/a/42542593
static const double A { 0.0776509570923569 }; static const double A { 0.0776509570923569 };
static const double B { -0.287434475393028 }; static const double B { -0.287434475393028 };
static const double C { ((HALF_PI/2) - A - B) }; static const double C { ((M_PI_4) - A - B) };
// polynominal factors for approximation between 1 and 5 // polynominal factors for approximation between 1 and 5
static const float C0 { 0.089494f }; static const float C0 { 0.089494f };
static const float C1 { 0.974207f }; static const float C1 { 0.974207f };
@ -102,7 +185,7 @@ float atan_t(float x) {
x = std::abs(x); x = std::abs(x);
float res; float res;
if (x > 5.0f) { // atan(x) converges to pi/2 - (1/x) for large values if (x > 5.0f) { // atan(x) converges to pi/2 - (1/x) for large values
res = HALF_PI - (1.0f/x); res = M_PI_2 - (1.0f/x);
} else if (x > 1.0f) { //1 < x < 5 } else if (x > 1.0f) { //1 < x < 5
float xx = x * x; float xx = x * x;
res = (C4*xx*xx)+(C3*xx*x)+(C2*xx)+(C1*x)+C0; res = (C4*xx*xx)+(C3*xx*x)+(C2*xx)+(C1*x)+C0;
@ -137,3 +220,27 @@ float fmod_t(float num, float denom) {
#endif #endif
return res; return res;
} }
// bit-wise integer square root calculation (exact)
uint32_t sqrt32_bw(uint32_t x) {
uint32_t res = 0;
uint32_t bit;
uint32_t num = x; // use 32bit for faster calculation
if(num < 1 << 10) bit = 1 << 10; // speed optimization for small numbers < 32^2
else if (num < 1 << 20) bit = 1 << 20; // speed optimization for medium numbers < 1024^2
else bit = 1 << 30; // start with highest power of 4 <= 2^32
while (bit > num) bit >>= 2; // reduce iterations
while (bit != 0) {
if (num >= res + bit) {
num -= res + bit;
res = (res >> 1) + bit;
} else {
res >>= 1;
}
bit >>= 2;
}
return res;
}

View File

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

View File

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

View File

@ -26,7 +26,7 @@ void XML_response(Print& dest)
); );
} }
static void extractPin(Print& settingsScript, JsonObject &obj, const char *key) { static void extractPin(Print& settingsScript, const JsonObject &obj, const char *key) {
if (obj[key].is<JsonArray>()) { if (obj[key].is<JsonArray>()) {
JsonArray pins = obj[key].as<JsonArray>(); JsonArray pins = obj[key].as<JsonArray>();
for (JsonVariant pv : pins) { 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) // 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) { for (JsonPair kv : mods) {
// kv.key() is usermod name or subobject key // kv.key() is usermod name or subobject key
@ -83,7 +83,7 @@ void appendGPIOinfo(Print& settingsScript) {
// usermod pin reservations will become unnecessary when settings pages will read cfg.json directly // usermod pin reservations will become unnecessary when settings pages will read cfg.json directly
if (requestJSONBufferLock(6)) { if (requestJSONBufferLock(6)) {
// if we can't allocate JSON buffer ignore usermod pins // if we can't allocate JSON buffer ignore usermod pins
JsonObject mods = pDoc->createNestedObject(F("um")); JsonObject mods = pDoc->createNestedObject("um");
UsermodManager::addToConfig(mods); UsermodManager::addToConfig(mods);
if (!mods.isNull()) fillUMPins(settingsScript, mods); if (!mods.isNull()) fillUMPins(settingsScript, mods);
releaseJSONBufferLock(); releaseJSONBufferLock();
@ -91,35 +91,42 @@ void appendGPIOinfo(Print& settingsScript) {
settingsScript.print(F("];")); settingsScript.print(F("];"));
// add reserved (unusable) pins // add reserved (unusable) pins
bool firstPin = true;
settingsScript.print(F("d.rsvd=[")); settingsScript.print(F("d.rsvd=["));
for (unsigned i = 0; i < WLED_NUM_PINS; i++) { for (unsigned i = 0; i < WLED_NUM_PINS; i++) {
if (!PinManager::isPinOk(i, false)) { // include readonly pins if (!PinManager::isPinOk(i, false)) { // include readonly pins
settingsScript.print(i); settingsScript.print(","); if (!firstPin) settingsScript.print(',');
settingsScript.print(i);
firstPin = false;
} }
} }
#ifdef WLED_ENABLE_DMX #ifdef WLED_ENABLE_DMX
settingsScript.print(F("2,")); // DMX hardcoded pin if (!firstPin) settingsScript.print(',');
settingsScript.print(2); // DMX hardcoded pin
firstPin = false;
#endif #endif
#if defined(WLED_DEBUG) && !defined(WLED_DEBUG_HOST) #if defined(WLED_DEBUG) && !defined(WLED_DEBUG_HOST)
settingsScript.printf_P(PSTR(",%d"),hardwareTX); // debug output (TX) pin if (!firstPin) settingsScript.print(',');
settingsScript.print(hardwareTX); // debug output (TX) pin
firstPin = false;
#endif #endif
//Note: Using pin 3 (RX) disables Adalight / Serial JSON
#ifdef WLED_USE_ETHERNET #ifdef WLED_USE_ETHERNET
if (ethernetType != WLED_ETH_NONE && ethernetType < WLED_NUM_ETH_TYPES) { if (ethernetType != WLED_ETH_NONE && ethernetType < WLED_NUM_ETH_TYPES) {
for (unsigned p=0; p<WLED_ETH_RSVD_PINS_COUNT; p++) { settingsScript.printf(",%d", esp32_nonconfigurable_ethernet_pins[p].pin); } if (!firstPin) settingsScript.print(',');
if (ethernetBoards[ethernetType].eth_power>=0) { settingsScript.printf(",%d", ethernetBoards[ethernetType].eth_power); } for (unsigned p=0; p<WLED_ETH_RSVD_PINS_COUNT; p++) { settingsScript.printf("%d,",esp32_nonconfigurable_ethernet_pins[p].pin); }
if (ethernetBoards[ethernetType].eth_mdc>=0) { settingsScript.printf(",%d", ethernetBoards[ethernetType].eth_mdc); } if (ethernetBoards[ethernetType].eth_power >= 0) { settingsScript.printf("%d,",ethernetBoards[ethernetType].eth_power); }
if (ethernetBoards[ethernetType].eth_mdio>=0) { settingsScript.printf(",%d", ethernetBoards[ethernetType].eth_mdio); } if (ethernetBoards[ethernetType].eth_mdc >= 0) { settingsScript.printf("%d,",ethernetBoards[ethernetType].eth_mdc); }
if (ethernetBoards[ethernetType].eth_mdio >= 0) { settingsScript.printf("%d,",ethernetBoards[ethernetType].eth_mdio); }
switch (ethernetBoards[ethernetType].eth_clk_mode) { switch (ethernetBoards[ethernetType].eth_clk_mode) {
case ETH_CLOCK_GPIO0_IN: case ETH_CLOCK_GPIO0_IN:
case ETH_CLOCK_GPIO0_OUT: case ETH_CLOCK_GPIO0_OUT:
settingsScript.print(F("0")); settingsScript.print(0);
break; break;
case ETH_CLOCK_GPIO16_OUT: case ETH_CLOCK_GPIO16_OUT:
settingsScript.print(F("16")); settingsScript.print(16);
break; break;
case ETH_CLOCK_GPIO17_OUT: case ETH_CLOCK_GPIO17_OUT:
settingsScript.print(F("17")); settingsScript.print(17);
break; break;
} }
} }
@ -128,11 +135,11 @@ void appendGPIOinfo(Print& settingsScript) {
// add info for read-only GPIO // add info for read-only GPIO
settingsScript.print(F("d.ro_gpio=[")); settingsScript.print(F("d.ro_gpio=["));
bool firstPin = true; firstPin = true;
for (unsigned i = 0; i < WLED_NUM_PINS; i++) { for (unsigned i = 0; i < WLED_NUM_PINS; i++) {
if (PinManager::isReadOnlyPin(i)) { if (PinManager::isReadOnlyPin(i)) {
// No comma before the first pin // No comma before the first pin
if (!firstPin) settingsScript.print(F(",")); if (!firstPin) settingsScript.print(',');
settingsScript.print(i); settingsScript.print(i);
firstPin = false; firstPin = false;
} }
@ -140,9 +147,7 @@ void appendGPIOinfo(Print& settingsScript) {
settingsScript.print(F("];")); settingsScript.print(F("];"));
// add info about max. # of pins // add info about max. # of pins
settingsScript.print(F("d.max_gpio=")); settingsScript.printf_P(PSTR("d.max_gpio=%d;"),WLED_NUM_PINS);
settingsScript.print(WLED_NUM_PINS);
settingsScript.print(F(";"));
} }
//get values for settings form in javascript //get values for settings form in javascript
@ -152,6 +157,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
DEBUG_PRINTF_P(PSTR("settings resp %u\n"), (unsigned)subPage); DEBUG_PRINTF_P(PSTR("settings resp %u\n"), (unsigned)subPage);
if (subPage <0 || subPage >10) return; if (subPage <0 || subPage >10) return;
char nS[32];
if (subPage == SUBPAGE_MENU) if (subPage == SUBPAGE_MENU)
{ {
@ -259,11 +265,9 @@ void getSettingsJS(byte subPage, Print& settingsScript)
if (subPage == SUBPAGE_LEDS) if (subPage == SUBPAGE_LEDS)
{ {
char nS[32];
appendGPIOinfo(settingsScript); appendGPIOinfo(settingsScript);
settingsScript.print(F("d.ledTypes=")); settingsScript.print(BusManager::getLEDTypesJSONString().c_str()); settingsScript.print(";"); settingsScript.printf_P(PSTR("d.ledTypes=%s;"), BusManager::getLEDTypesJSONString().c_str());
// set limits // set limits
settingsScript.printf_P(PSTR("bLimits(%d,%d,%d,%d,%d,%d,%d,%d);"), settingsScript.printf_P(PSTR("bLimits(%d,%d,%d,%d,%d,%d,%d,%d);"),
@ -281,7 +285,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
printSetFormCheckbox(settingsScript,PSTR("CCT"),strip.correctWB); printSetFormCheckbox(settingsScript,PSTR("CCT"),strip.correctWB);
printSetFormCheckbox(settingsScript,PSTR("IC"),cctICused); printSetFormCheckbox(settingsScript,PSTR("IC"),cctICused);
printSetFormCheckbox(settingsScript,PSTR("CR"),strip.cctFromRgb); 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("FR"),strip.getTargetFps());
printSetFormValue(settingsScript,PSTR("AW"),Bus::getGlobalAWMode()); printSetFormValue(settingsScript,PSTR("AW"),Bus::getGlobalAWMode());
printSetFormCheckbox(settingsScript,PSTR("LD"),useGlobalLedBuffer); printSetFormCheckbox(settingsScript,PSTR("LD"),useGlobalLedBuffer);
@ -365,10 +369,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
printSetFormCheckbox(settingsScript,PSTR("GB"),gammaCorrectBri); printSetFormCheckbox(settingsScript,PSTR("GB"),gammaCorrectBri);
printSetFormCheckbox(settingsScript,PSTR("GC"),gammaCorrectCol); printSetFormCheckbox(settingsScript,PSTR("GC"),gammaCorrectCol);
dtostrf(gammaCorrectVal,3,1,nS); printSetFormValue(settingsScript,PSTR("GV"),nS); 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); printSetFormValue(settingsScript,PSTR("TD"),transitionDelayDefault);
printSetFormCheckbox(settingsScript,PSTR("PF"),strip.paletteFade);
printSetFormValue(settingsScript,PSTR("TP"),randomPaletteChangeTime); printSetFormValue(settingsScript,PSTR("TP"),randomPaletteChangeTime);
printSetFormCheckbox(settingsScript,PSTR("TH"),useHarmonicRandomPalette); printSetFormCheckbox(settingsScript,PSTR("TH"),useHarmonicRandomPalette);
printSetFormValue(settingsScript,PSTR("BF"),briMultiplier); printSetFormValue(settingsScript,PSTR("BF"),briMultiplier);
@ -399,7 +400,6 @@ void getSettingsJS(byte subPage, Print& settingsScript)
if (subPage == SUBPAGE_SYNC) if (subPage == SUBPAGE_SYNC)
{ {
[[maybe_unused]] char nS[32];
printSetFormValue(settingsScript,PSTR("UP"),udpPort); printSetFormValue(settingsScript,PSTR("UP"),udpPort);
printSetFormValue(settingsScript,PSTR("U2"),udpPort2); printSetFormValue(settingsScript,PSTR("U2"),udpPort2);
#ifndef WLED_DISABLE_ESPNOW #ifndef WLED_DISABLE_ESPNOW
@ -433,6 +433,18 @@ void getSettingsJS(byte subPage, Print& settingsScript)
printSetFormCheckbox(settingsScript,PSTR("ES"),e131SkipOutOfSequence); printSetFormCheckbox(settingsScript,PSTR("ES"),e131SkipOutOfSequence);
printSetFormCheckbox(settingsScript,PSTR("EM"),e131Multicast); printSetFormCheckbox(settingsScript,PSTR("EM"),e131Multicast);
printSetFormValue(settingsScript,PSTR("EU"),e131Universe); 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("DA"),DMXAddress);
printSetFormValue(settingsScript,PSTR("XX"),DMXSegmentSpacing); printSetFormValue(settingsScript,PSTR("XX"),DMXSegmentSpacing);
printSetFormValue(settingsScript,PSTR("PY"),e131Priority); printSetFormValue(settingsScript,PSTR("PY"),e131Priority);
@ -465,7 +477,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
printSetFormValue(settingsScript,PSTR("MG"),mqttGroupTopic); printSetFormValue(settingsScript,PSTR("MG"),mqttGroupTopic);
printSetFormCheckbox(settingsScript,PSTR("BM"),buttonPublishMqtt); printSetFormCheckbox(settingsScript,PSTR("BM"),buttonPublishMqtt);
printSetFormCheckbox(settingsScript,PSTR("RT"),retainMqttMsg); printSetFormCheckbox(settingsScript,PSTR("RT"),retainMqttMsg);
settingsScript.printf_P(PSTR("d.Sf.MD.maxlength=%d;d.Sf.MG.maxlength=%d;d.Sf.MS.maxlength=%d;"), settingsScript.printf_P(PSTR("d.Sf.MD.maxLength=%d;d.Sf.MG.maxLength=%d;d.Sf.MS.maxLength=%d;"),
MQTT_MAX_TOPIC_LEN, MQTT_MAX_TOPIC_LEN, MQTT_MAX_SERVER_LEN); MQTT_MAX_TOPIC_LEN, MQTT_MAX_TOPIC_LEN, MQTT_MAX_SERVER_LEN);
#else #else
settingsScript.print(F("toggle('MQTT');")); // hide MQTT settings settingsScript.print(F("toggle('MQTT');")); // hide MQTT settings
@ -637,7 +649,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
#if defined(ARDUINO_ARCH_ESP32) #if defined(ARDUINO_ARCH_ESP32)
ESP.getChipModel(), ESP.getChipModel(),
#else #else
F("esp8266"), "esp8266",
#endif #endif
VERSION); VERSION);
@ -648,8 +660,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
{ {
printSetFormValue(settingsScript,PSTR("SOMP"),strip.isMatrix); printSetFormValue(settingsScript,PSTR("SOMP"),strip.isMatrix);
#ifndef WLED_DISABLE_2D #ifndef WLED_DISABLE_2D
settingsScript.printf_P(PSTR("maxPanels=%d;"),WLED_MAX_PANELS); settingsScript.printf_P(PSTR("maxPanels=%d;resetPanels();"),WLED_MAX_PANELS);
settingsScript.print(F("resetPanels();"));
if (strip.isMatrix) { if (strip.isMatrix) {
if(strip.panels>0){ if(strip.panels>0){
printSetFormValue(settingsScript,PSTR("PW"),strip.panel[0].width); //Set generator Width and Height to first panel size for convenience printSetFormValue(settingsScript,PSTR("PW"),strip.panel[0].width); //Set generator Width and Height to first panel size for convenience
@ -658,12 +669,9 @@ void getSettingsJS(byte subPage, Print& settingsScript)
printSetFormValue(settingsScript,PSTR("MPC"),strip.panels); printSetFormValue(settingsScript,PSTR("MPC"),strip.panels);
// panels // panels
for (unsigned i=0; i<strip.panels; i++) { for (unsigned i=0; i<strip.panels; i++) {
char n[5]; settingsScript.printf_P(PSTR("addPanel(%d);"), i);
settingsScript.print(F("addPanel("));
settingsScript.print(itoa(i,n,10));
settingsScript.print(F(");"));
char pO[8] = { '\0' }; char pO[8] = { '\0' };
snprintf_P(pO, 7, PSTR("P%d"), i); // MAX_PANELS is 64 so pO will always only be 4 characters or less snprintf_P(pO, 7, PSTR("P%d"), i); // WLED_MAX_PANELS is 18 so pO will always only be 4 characters or less
pO[7] = '\0'; pO[7] = '\0';
unsigned l = strlen(pO); unsigned l = strlen(pO);
// create P0B, P1B, ..., P63B, etc for other PxxX // create P0B, P1B, ..., P63B, etc for other PxxX