Compare commits

..

6 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
8590538272 Use min() to prevent increasing bus count and maintain user configuration
Co-authored-by: DedeHai <6280424+DedeHai@users.noreply.github.com>
2025-09-15 06:26:11 +00:00
copilot-swe-agent[bot]
f75b13a29a Simplify bus creation logic as requested - replace complex binary search with simple default count fallback
Co-authored-by: DedeHai <6280424+DedeHai@users.noreply.github.com>
2025-09-15 06:15:20 +00:00
copilot-swe-agent[bot]
1b19c35625 Add comprehensive UI error handling for empty segment lists
Co-authored-by: DedeHai <6280424+DedeHai@users.noreply.github.com>
2025-09-15 05:45:21 +00:00
copilot-swe-agent[bot]
2dfea5c1e2 Improve bus creation with memory-aware truncation and UI error handling
Co-authored-by: DedeHai <6280424+DedeHai@users.noreply.github.com>
2025-09-15 05:10:24 +00:00
copilot-swe-agent[bot]
a441ee81b6 Implement bus creation fallback when memory limit exceeded
Co-authored-by: DedeHai <6280424+DedeHai@users.noreply.github.com>
2025-09-15 04:52:35 +00:00
copilot-swe-agent[bot]
6c0c875fe6 Initial plan 2025-09-15 04:43:20 +00:00
60 changed files with 1331 additions and 4173 deletions

View File

@@ -30,27 +30,6 @@ The build has two main phases:
- Common environments: `nodemcuv2`, `esp32dev`, `esp8266_2m`
- List all targets: `pio run --list-targets`
## Before Finishing Work
**CRITICAL: You MUST complete ALL of these steps before marking your work as complete:**
1. **Run the test suite**: `npm test` -- Set timeout to 2+ minutes. NEVER CANCEL.
- All tests MUST pass
- If tests fail, fix the issue before proceeding
2. **Build at least one hardware environment**: `pio run -e esp32dev` -- Set timeout to 30+ minutes. NEVER CANCEL.
- Choose `esp32dev` as it's a common, representative environment
- See "Hardware Compilation" section above for the full list of common environments
- The build MUST complete successfully without errors
- If the build fails, fix the issue before proceeding
- **DO NOT skip this step** - it validates that firmware compiles with your changes
3. **For web UI changes only**: Manually test the interface
- See "Manual Testing Scenarios" section below
- Verify the UI loads and functions correctly
**If any of these validation steps fail, you MUST fix the issues before finishing. Do NOT mark work as complete with failing builds or tests.**
## Validation and Testing
### Web UI Testing
@@ -65,7 +44,6 @@ The build has two main phases:
- **Code style**: Use tabs for web files (.html/.css/.js), spaces (2 per level) for C++ files
- **C++ formatting available**: `clang-format` is installed but not in CI
- **Always run tests before finishing**: `npm test`
- **MANDATORY: Always run a hardware build before finishing** (see "Before Finishing Work" section below)
### Manual Testing Scenarios
After making changes to web UI, always test:
@@ -121,16 +99,10 @@ package.json # Node.js dependencies and scripts
## Build Timing and Timeouts
**IMPORTANT: Use these timeout values when running builds:**
- **Web UI build** (`npm run build`): 3 seconds typical - Set timeout to 30 seconds minimum
- **Test suite** (`npm test`): 40 seconds typical - Set timeout to 120 seconds (2 minutes) minimum
- **Hardware builds** (`pio run -e [target]`): 15-20 minutes typical for first build - Set timeout to 1800 seconds (30 minutes) minimum
- Subsequent builds are faster due to caching
- First builds download toolchains and dependencies which takes significant time
- **NEVER CANCEL long-running builds** - PlatformIO downloads and compilation require patience
**When validating your changes before finishing, you MUST wait for the hardware build to complete successfully. Set the timeout appropriately and be patient.**
- **Web UI build**: 3 seconds - Set timeout to 30 seconds minimum
- **Test suite**: 40 seconds - Set timeout to 2 minutes minimum
- **Hardware builds**: 15+ minutes - Set timeout to 30+ minutes minimum
- **NEVER CANCEL long-running builds** - PlatformIO downloads and compilation can take significant time
## Troubleshooting
@@ -156,17 +128,11 @@ package.json # Node.js dependencies and scripts
- **Hardware builds require appropriate ESP32/ESP8266 development board**
## CI/CD Pipeline
**The GitHub Actions CI workflow will:**
The GitHub Actions workflow:
1. Installs Node.js and Python dependencies
2. Runs `npm test` to validate build system (MUST pass)
3. Builds web UI with `npm run build` (automatically run by PlatformIO)
4. Compiles firmware for ALL hardware targets listed in `default_envs` (MUST succeed for all)
2. Runs `npm test` to validate build system
3. Builds web UI with `npm run build`
4. Compiles firmware for multiple hardware targets
5. Uploads build artifacts
**To ensure CI success, you MUST locally:**
- Run `npm test` and ensure it passes
- Run `pio run -e esp32dev` (or another common environment from "Hardware Compilation" section) and ensure it completes successfully
- If either fails locally, it WILL fail in CI
**Match this workflow in your local development to ensure CI success. Do not mark work complete until you have validated builds locally.**
Match this workflow in your local development to ensure CI success.

View File

@@ -33,6 +33,6 @@
run: |
jq -n \
--arg content "Pull Request #${PR_NUMBER} \"${PR_TITLE}\" merged by ${ACTOR}
${PR_URL} . It will be included in the next nightly builds, please test" \
${PR_URL}. It will be included in the next nightly builds, please test" \
'{content: $content}' \
| curl -H "Content-Type: application/json" -d @- ${{ secrets.DISCORD_WEBHOOK_BETA_TESTERS }}

2
.gitignore vendored
View File

@@ -7,8 +7,6 @@
.pioenvs
.piolibdeps
.vscode
compile_commands.json
__pycache__/
esp01-update.sh
platformio_override.ini

View File

@@ -1,47 +0,0 @@
{
"build": {
"arduino":{
"ldscript": "esp32s3_out.ld",
"memory_type": "qio_opi",
"partitions": "default_16MB.csv"
},
"core": "esp32",
"extra_flags": [
"-DARDUINO_TTGO_T7_S3",
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_MODE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"hwids": [
[
"0X303A",
"0x1001"
]
],
"mcu": "esp32s3",
"variant": "esp32s3"
},
"connectivity": [
"wifi",
"bluetooth"
],
"debug": {
"openocd_target": "esp32s3.cfg"
},
"frameworks": [
"arduino",
"espidf"
],
"name": "LILYGO T3-S3",
"upload": {
"flash_size": "16MB",
"maximum_ram_size": 327680,
"maximum_size": 16777216,
"require_upload_port": true,
"speed": 921600
},
"url": "https://www.aliexpress.us/item/3256804591247074.html",
"vendor": "LILYGO"
}

View File

@@ -1,469 +0,0 @@
/*-------------------------------------------------------------------------
NeoPixel driver for ESP32 RMTs using High-priority Interrupt
(NB. This cannot be mixed with the non-HI driver.)
Written by Will M. Miles.
I invest time and resources providing this open source code,
please support me by donating (see https://github.com/Makuna/NeoPixelBus)
-------------------------------------------------------------------------
This file is part of the Makuna/NeoPixelBus library.
NeoPixelBus 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 3 of
the License, or (at your option) any later version.
NeoPixelBus 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 NeoPixel. If not, see
<http://www.gnu.org/licenses/>.
-------------------------------------------------------------------------*/
#pragma once
#if defined(ARDUINO_ARCH_ESP32)
// Use the NeoEspRmtSpeed types from the driver-based implementation
#include <NeoPixelBus.h>
namespace NeoEsp32RmtHiMethodDriver {
// Install the driver for a specific channel, specifying timing properties
esp_err_t Install(rmt_channel_t channel, uint32_t rmtBit0, uint32_t rmtBit1, uint32_t resetDuration);
// Remove the driver on a specific channel
esp_err_t Uninstall(rmt_channel_t channel);
// Write a buffer of data to a specific channel.
// Buffer reference is held until write completes.
esp_err_t Write(rmt_channel_t channel, const uint8_t *src, size_t src_size);
// Wait until transaction is complete.
esp_err_t WaitForTxDone(rmt_channel_t channel, TickType_t wait_time);
};
template<typename T_SPEED, typename T_CHANNEL> class NeoEsp32RmtHIMethodBase
{
public:
typedef NeoNoSettings SettingsObject;
NeoEsp32RmtHIMethodBase(uint8_t pin, uint16_t pixelCount, size_t elementSize, size_t settingsSize) :
_sizeData(pixelCount * elementSize + settingsSize),
_pin(pin)
{
construct();
}
NeoEsp32RmtHIMethodBase(uint8_t pin, uint16_t pixelCount, size_t elementSize, size_t settingsSize, NeoBusChannel channel) :
_sizeData(pixelCount* elementSize + settingsSize),
_pin(pin),
_channel(channel)
{
construct();
}
~NeoEsp32RmtHIMethodBase()
{
// wait until the last send finishes before destructing everything
// arbitrary time out of 10 seconds
ESP_ERROR_CHECK_WITHOUT_ABORT(NeoEsp32RmtHiMethodDriver::WaitForTxDone(_channel.RmtChannelNumber, 10000 / portTICK_PERIOD_MS));
ESP_ERROR_CHECK(NeoEsp32RmtHiMethodDriver::Uninstall(_channel.RmtChannelNumber));
gpio_matrix_out(_pin, SIG_GPIO_OUT_IDX, false, false);
pinMode(_pin, INPUT);
free(_dataEditing);
free(_dataSending);
}
bool IsReadyToUpdate() const
{
return (ESP_OK == ESP_ERROR_CHECK_WITHOUT_ABORT_SILENT_TIMEOUT(NeoEsp32RmtHiMethodDriver::WaitForTxDone(_channel.RmtChannelNumber, 0)));
}
void Initialize()
{
rmt_config_t config = {};
config.rmt_mode = RMT_MODE_TX;
config.channel = _channel.RmtChannelNumber;
config.gpio_num = static_cast<gpio_num_t>(_pin);
config.mem_block_num = 1;
config.tx_config.loop_en = false;
config.tx_config.idle_output_en = true;
config.tx_config.idle_level = T_SPEED::IdleLevel;
config.tx_config.carrier_en = false;
config.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW;
config.clk_div = T_SPEED::RmtClockDivider;
ESP_ERROR_CHECK(rmt_config(&config)); // Uses ESP library
ESP_ERROR_CHECK(NeoEsp32RmtHiMethodDriver::Install(_channel.RmtChannelNumber, T_SPEED::RmtBit0, T_SPEED::RmtBit1, T_SPEED::RmtDurationReset));
}
void Update(bool maintainBufferConsistency)
{
// wait for not actively sending data
// this will time out at 10 seconds, an arbitrarily long period of time
// and do nothing if this happens
if (ESP_OK == ESP_ERROR_CHECK_WITHOUT_ABORT(NeoEsp32RmtHiMethodDriver::WaitForTxDone(_channel.RmtChannelNumber, 10000 / portTICK_PERIOD_MS)))
{
// now start the RMT transmit with the editing buffer before we swap
ESP_ERROR_CHECK_WITHOUT_ABORT(NeoEsp32RmtHiMethodDriver::Write(_channel.RmtChannelNumber, _dataEditing, _sizeData));
if (maintainBufferConsistency)
{
// copy editing to sending,
// this maintains the contract that "colors present before will
// be the same after", otherwise GetPixelColor will be inconsistent
memcpy(_dataSending, _dataEditing, _sizeData);
}
// swap so the user can modify without affecting the async operation
std::swap(_dataSending, _dataEditing);
}
}
bool AlwaysUpdate()
{
// this method requires update to be called only if changes to buffer
return false;
}
bool SwapBuffers()
{
std::swap(_dataSending, _dataEditing);
return true;
}
uint8_t* getData() const
{
return _dataEditing;
};
size_t getDataSize() const
{
return _sizeData;
}
void applySettings([[maybe_unused]] const SettingsObject& settings)
{
}
private:
const size_t _sizeData; // Size of '_data*' buffers
const uint8_t _pin; // output pin number
const T_CHANNEL _channel; // holds instance for multi channel support
// Holds data stream which include LED color values and other settings as needed
uint8_t* _dataEditing; // exposed for get and set
uint8_t* _dataSending; // used for async send using RMT
void construct()
{
_dataEditing = static_cast<uint8_t*>(malloc(_sizeData));
// data cleared later in Begin()
_dataSending = static_cast<uint8_t*>(malloc(_sizeData));
// no need to initialize it, it gets overwritten on every send
}
};
// normal
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannelN> NeoEsp32RmtHINWs2811Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannelN> NeoEsp32RmtHINWs2812xMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannelN> NeoEsp32RmtHINWs2816Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannelN> NeoEsp32RmtHINWs2805Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannelN> NeoEsp32RmtHINSk6812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannelN> NeoEsp32RmtHINTm1814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannelN> NeoEsp32RmtHINTm1829Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannelN> NeoEsp32RmtHINTm1914Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannelN> NeoEsp32RmtHINApa106Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannelN> NeoEsp32RmtHINTx1812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannelN> NeoEsp32RmtHINGs1903Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannelN> NeoEsp32RmtHIN800KbpsMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannelN> NeoEsp32RmtHIN400KbpsMethod;
typedef NeoEsp32RmtHINWs2805Method NeoEsp32RmtHINWs2814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Ws2811Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Ws2812xMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Ws2816Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Ws2805Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Sk6812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Tm1814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Tm1829Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Tm1914Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Apa106Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Tx1812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Gs1903Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannel0> NeoEsp32RmtHI0800KbpsMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannel0> NeoEsp32RmtHI0400KbpsMethod;
typedef NeoEsp32RmtHI0Ws2805Method NeoEsp32RmtHI0Ws2814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Ws2811Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Ws2812xMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Ws2816Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Ws2805Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Sk6812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Tm1814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Tm1829Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Tm1914Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Apa106Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Tx1812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Gs1903Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannel1> NeoEsp32RmtHI1800KbpsMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannel1> NeoEsp32RmtHI1400KbpsMethod;
typedef NeoEsp32RmtHI1Ws2805Method NeoEsp32RmtHI1Ws2814Method;
#if !defined(CONFIG_IDF_TARGET_ESP32C3)
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Ws2811Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Ws2812xMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Ws2816Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Ws2805Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Sk6812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Tm1814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Tm1829Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Tm1914Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Apa106Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Tx1812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Gs1903Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannel2> NeoEsp32RmtHI2800KbpsMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannel2> NeoEsp32RmtHI2400KbpsMethod;
typedef NeoEsp32RmtHI2Ws2805Method NeoEsp32RmtHI2Ws2814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Ws2811Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Ws2812xMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Ws2816Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Ws2805Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Sk6812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Tm1814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Tm1829Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Tm1914Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Apa106Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Tx1812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Gs1903Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannel3> NeoEsp32RmtHI3800KbpsMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannel3> NeoEsp32RmtHI3400KbpsMethod;
typedef NeoEsp32RmtHI3Ws2805Method NeoEsp32RmtHI3Ws2814Method;
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3)
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Ws2811Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Ws2812xMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Ws2816Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Ws2805Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Sk6812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Tm1814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Tm1829Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Tm1914Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Apa106Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Tx1812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Gs1903Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannel4> NeoEsp32RmtHI4800KbpsMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannel4> NeoEsp32RmtHI4400KbpsMethod;
typedef NeoEsp32RmtHI4Ws2805Method NeoEsp32RmtHI4Ws2814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Ws2811Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Ws2812xMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Ws2816Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Ws2805Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Sk6812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Tm1814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Tm1829Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Tm1914Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Apa106Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Tx1812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Gs1903Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannel5> NeoEsp32RmtHI5800KbpsMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannel5> NeoEsp32RmtHI5400KbpsMethod;
typedef NeoEsp32RmtHI5Ws2805Method NeoEsp32RmtHI5Ws2814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Ws2811Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Ws2812xMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Ws2816Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Ws2805Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Sk6812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Tm1814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Tm1829Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Tm1914Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Apa106Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Tx1812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Gs1903Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannel6> NeoEsp32RmtHI6800KbpsMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannel6> NeoEsp32RmtHI6400KbpsMethod;
typedef NeoEsp32RmtHI6Ws2805Method NeoEsp32RmtHI6Ws2814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Ws2811Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Ws2812xMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Ws2816Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Ws2805Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Sk6812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Tm1814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Tm1829Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Tm1914Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Apa106Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Tx1812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Gs1903Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannel7> NeoEsp32RmtHI7800KbpsMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannel7> NeoEsp32RmtHI7400KbpsMethod;
typedef NeoEsp32RmtHI7Ws2805Method NeoEsp32RmtHI7Ws2814Method;
#endif // !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3)
#endif // !defined(CONFIG_IDF_TARGET_ESP32C3)
// inverted
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannelN> NeoEsp32RmtHINWs2811InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannelN> NeoEsp32RmtHINWs2812xInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannelN> NeoEsp32RmtHINWs2816InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannelN> NeoEsp32RmtHINWs2805InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannelN> NeoEsp32RmtHINSk6812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannelN> NeoEsp32RmtHINTm1814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannelN> NeoEsp32RmtHINTm1829InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannelN> NeoEsp32RmtHINTm1914InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannelN> NeoEsp32RmtHINApa106InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannelN> NeoEsp32RmtHINTx1812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannelN> NeoEsp32RmtHINGs1903InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannelN> NeoEsp32RmtHIN800KbpsInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannelN> NeoEsp32RmtHIN400KbpsInvertedMethod;
typedef NeoEsp32RmtHINWs2805InvertedMethod NeoEsp32RmtHINWs2814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Ws2811InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Ws2812xInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Ws2816InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Ws2805InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Sk6812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Tm1814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Tm1829InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Tm1914InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Apa106InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Tx1812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Gs1903InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannel0> NeoEsp32RmtHI0800KbpsInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannel0> NeoEsp32RmtHI0400KbpsInvertedMethod;
typedef NeoEsp32RmtHI0Ws2805InvertedMethod NeoEsp32RmtHI0Ws2814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Ws2811InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Ws2812xInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Ws2816InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Ws2805InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Sk6812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Tm1814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Tm1829InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Tm1914InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Apa106InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Tx1812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Gs1903InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannel1> NeoEsp32RmtHI1800KbpsInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannel1> NeoEsp32RmtHI1400KbpsInvertedMethod;
typedef NeoEsp32RmtHI1Ws2805InvertedMethod NeoEsp32RmtHI1Ws2814InvertedMethod;
#if !defined(CONFIG_IDF_TARGET_ESP32C3)
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Ws2811InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Ws2812xInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Ws2816InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Ws2805InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Sk6812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Tm1814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Tm1829InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Tm1914InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Apa106InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Tx1812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Gs1903InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannel2> NeoEsp32RmtHI2800KbpsInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannel2> NeoEsp32RmtHI2400KbpsInvertedMethod;
typedef NeoEsp32RmtHI2Ws2805InvertedMethod NeoEsp32RmtHI2Ws2814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Ws2811InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Ws2812xInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Ws2805InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Ws2816InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Sk6812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Tm1814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Tm1829InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Tm1914InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Apa106InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Tx1812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Gs1903InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannel3> NeoEsp32RmtHI3800KbpsInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannel3> NeoEsp32RmtHI3400KbpsInvertedMethod;
typedef NeoEsp32RmtHI3Ws2805InvertedMethod NeoEsp32RmtHI3Ws2814InvertedMethod;
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3)
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Ws2811InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Ws2812xInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Ws2816InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Ws2805InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Sk6812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Tm1814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Tm1829InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Tm1914InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Apa106InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Tx1812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Gs1903InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannel4> NeoEsp32RmtHI4800KbpsInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannel4> NeoEsp32RmtHI4400KbpsInvertedMethod;
typedef NeoEsp32RmtHI4Ws2805InvertedMethod NeoEsp32RmtHI4Ws2814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Ws2811InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Ws2812xInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Ws2816InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Ws2805InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Sk6812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Tm1814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Tm1829InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Tm1914InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Apa106InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Tx1812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Gs1903InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannel5> NeoEsp32RmtHI5800KbpsInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannel5> NeoEsp32RmtHI5400KbpsInvertedMethod;
typedef NeoEsp32RmtHI5Ws2805InvertedMethod NeoEsp32RmtHI5Ws2814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Ws2811InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Ws2812xInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Ws2816InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Ws2805InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Sk6812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Tm1814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Tm1829InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Tm1914InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Apa106InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Tx1812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Gs1903InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannel6> NeoEsp32RmtHI6800KbpsInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannel6> NeoEsp32RmtHI6400KbpsInvertedMethod;
typedef NeoEsp32RmtHI6Ws2805InvertedMethod NeoEsp32RmtHI6Ws2814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Ws2811InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Ws2812xInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Ws2816InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Ws2805InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Sk6812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Tm1814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Tm1829InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Tm1914InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Apa106InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Tx1812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Gs1903InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannel7> NeoEsp32RmtHI7800KbpsInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannel7> NeoEsp32RmtHI7400KbpsInvertedMethod;
typedef NeoEsp32RmtHI7Ws2805InvertedMethod NeoEsp32RmtHI7Ws2814InvertedMethod;
#endif // !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3)
#endif // !defined(CONFIG_IDF_TARGET_ESP32C3)
#endif

View File

@@ -1,12 +0,0 @@
{
"name": "NeoESP32RmtHI",
"build": { "libArchive": false },
"platforms": ["espressif32"],
"dependencies": [
{
"owner": "makuna",
"name": "NeoPixelBus",
"version": "^2.8.3"
}
]
}

View File

@@ -1,263 +0,0 @@
/* RMT ISR shim
* Bridges from a high-level interrupt to the C++ code.
*
* This code is largely derived from Espressif's 'hli_vector.S' Bluetooth ISR.
*
*/
#if defined(__XTENSA__) && defined(ESP32) && !defined(CONFIG_BTDM_CTRL_HLI)
#include <freertos/xtensa_context.h>
#include "sdkconfig.h"
#include "soc/soc.h"
/* If the Bluetooth driver has hooked the high-priority interrupt, we piggyback on it and don't need this. */
#ifndef CONFIG_BTDM_CTRL_HLI
/*
Select interrupt based on system check level
- Base ESP32: could be 4 or 5, depends on platform config
- S2: 5
- S3: 5
*/
#if CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5
/* Use level 4 */
#define RFI_X 4
#define xt_highintx xt_highint4
#else /* !CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5 */
/* Use level 5 */
#define RFI_X 5
#define xt_highintx xt_highint5
#endif /* CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5 */
// Register map, based on interrupt level
#define EPC_X (EPC + RFI_X)
#define EXCSAVE_X (EXCSAVE + RFI_X)
// The sp mnemonic is used all over in ESP's assembly, though I'm not sure where it's expected to be defined?
#define sp a1
/* Interrupt stack size, for C code. */
#define RMT_INTR_STACK_SIZE 512
/* Save area for the CPU state:
* - 64 words for the general purpose registers
* - 7 words for some of the special registers:
* - WINDOWBASE, WINDOWSTART only WINDOWSTART is truly needed
* - SAR, LBEG, LEND, LCOUNT since the C code might use these
* - EPC1 since the C code might cause window overflow exceptions
* This is not laid out as standard exception frame structure
* for simplicity of the save/restore code.
*/
#define REG_FILE_SIZE (64 * 4)
#define SPECREG_OFFSET REG_FILE_SIZE
#define SPECREG_SIZE (7 * 4)
#define REG_SAVE_AREA_SIZE (SPECREG_OFFSET + SPECREG_SIZE)
.data
_rmt_intr_stack:
.space RMT_INTR_STACK_SIZE
_rmt_save_ctx:
.space REG_SAVE_AREA_SIZE
.section .iram1,"ax"
.global xt_highintx
.type xt_highintx,@function
.align 4
xt_highintx:
movi a0, _rmt_save_ctx
/* save 4 lower registers */
s32i a1, a0, 4
s32i a2, a0, 8
s32i a3, a0, 12
rsr a2, EXCSAVE_X /* holds the value of a0 */
s32i a2, a0, 0
/* Save special registers */
addi a0, a0, SPECREG_OFFSET
rsr a2, WINDOWBASE
s32i a2, a0, 0
rsr a2, WINDOWSTART
s32i a2, a0, 4
rsr a2, SAR
s32i a2, a0, 8
#if XCHAL_HAVE_LOOPS
rsr a2, LBEG
s32i a2, a0, 12
rsr a2, LEND
s32i a2, a0, 16
rsr a2, LCOUNT
s32i a2, a0, 20
#endif
rsr a2, EPC1
s32i a2, a0, 24
/* disable exception mode, window overflow */
movi a0, PS_INTLEVEL(RFI_X+1) | PS_EXCM
wsr a0, PS
rsync
/* Save the remaining physical registers.
* 4 registers are already saved, which leaves 60 registers to save.
* (FIXME: consider the case when the CPU is configured with physical 32 registers)
* These 60 registers are saved in 5 iterations, 12 registers at a time.
*/
movi a1, 5
movi a3, _rmt_save_ctx + 4 * 4
/* This is repeated 5 times, each time the window is shifted by 12 registers.
* We come here with a1 = downcounter, a3 = save pointer, a2 and a0 unused.
*/
1:
s32i a4, a3, 0
s32i a5, a3, 4
s32i a6, a3, 8
s32i a7, a3, 12
s32i a8, a3, 16
s32i a9, a3, 20
s32i a10, a3, 24
s32i a11, a3, 28
s32i a12, a3, 32
s32i a13, a3, 36
s32i a14, a3, 40
s32i a15, a3, 44
/* We are about to rotate the window, so that a12-a15 will become the new a0-a3.
* Copy a0-a3 to a12-15 to still have access to these values.
* At the same time we can decrement the counter and adjust the save area pointer
*/
/* a0 is constant (_rmt_save_ctx), no need to copy */
addi a13, a1, -1 /* copy and decrement the downcounter */
/* a2 is scratch so no need to copy */
addi a15, a3, 48 /* copy and adjust the save area pointer */
beqz a13, 2f /* have saved all registers ? */
rotw 3 /* rotate the window and go back */
j 1b
/* the loop is complete */
2:
rotw 4 /* this brings us back to the original window */
/* a0 still points to _rmt_save_ctx */
/* Can clear WINDOWSTART now, all registers are saved */
rsr a2, WINDOWBASE
/* WINDOWSTART = (1 << WINDOWBASE) */
movi a3, 1
ssl a2
sll a3, a3
wsr a3, WINDOWSTART
_highint_stack_switch:
movi a0, 0
movi sp, _rmt_intr_stack + RMT_INTR_STACK_SIZE - 16
s32e a0, sp, -12 /* For GDB: set null SP */
s32e a0, sp, -16 /* For GDB: set null PC */
movi a0, _highint_stack_switch /* For GDB: cosmetics, for the frame where stack switch happened */
/* Set up PS for C, disable all interrupts except NMI and debug, and clear EXCM. */
movi a6, PS_INTLEVEL(RFI_X) | PS_UM | PS_WOE
wsr a6, PS
rsync
/* Call C handler */
mov a6, sp
call4 NeoEsp32RmtMethodIsr
l32e sp, sp, -12 /* switch back to the original stack */
/* Done with C handler; re-enable exception mode, disabling window overflow */
movi a2, PS_INTLEVEL(RFI_X+1) | PS_EXCM /* TOCHECK */
wsr a2, PS
rsync
/* Restore the special registers.
* WINDOWSTART will be restored near the end.
*/
movi a0, _rmt_save_ctx + SPECREG_OFFSET
l32i a2, a0, 8
wsr a2, SAR
#if XCHAL_HAVE_LOOPS
l32i a2, a0, 12
wsr a2, LBEG
l32i a2, a0, 16
wsr a2, LEND
l32i a2, a0, 20
wsr a2, LCOUNT
#endif
l32i a2, a0, 24
wsr a2, EPC1
/* Restoring the physical registers.
* This is the reverse to the saving process above.
*/
/* Rotate back to the final window, then start loading 12 registers at a time,
* in 5 iterations.
* Again, a1 is the downcounter and a3 is the save area pointer.
* After each rotation, a1 and a3 are copied from a13 and a15.
* To simplify the loop, we put the initial values into a13 and a15.
*/
rotw -4
movi a15, _rmt_save_ctx + 64 * 4 /* point to the end of the save area */
movi a13, 5
1:
/* Copy a1 and a3 from their previous location,
* at the same time decrementing and adjusting the save area pointer.
*/
addi a1, a13, -1
addi a3, a15, -48
/* Load 12 registers */
l32i a4, a3, 0
l32i a5, a3, 4
l32i a6, a3, 8
l32i a7, a3, 12
l32i a8, a3, 16
l32i a9, a3, 20
l32i a10, a3, 24
l32i a11, a3, 28 /* ensure PS and EPC written */
l32i a12, a3, 32
l32i a13, a3, 36
l32i a14, a3, 40
l32i a15, a3, 44
/* Done with the loop? */
beqz a1, 2f
/* If no, rotate the window and repeat */
rotw -3
j 1b
2:
/* Done with the loop. Only 4 registers (a0-a3 in the original window) remain
* to be restored. Also need to restore WINDOWSTART, since all the general
* registers are now in place.
*/
movi a0, _rmt_save_ctx
l32i a2, a0, SPECREG_OFFSET + 4
wsr a2, WINDOWSTART
l32i a1, a0, 4
l32i a2, a0, 8
l32i a3, a0, 12
rsr a0, EXCSAVE_X /* holds the value of a0 before the interrupt handler */
/* Return from the interrupt, restoring PS from EPS_X */
rfi RFI_X
/* The linker has no reason to link in this file; all symbols it exports are already defined
(weakly!) in the default int handler. Define a symbol here so we can use it to have the
linker inspect this anyway. */
.global ld_include_hli_vectors_rmt
ld_include_hli_vectors_rmt:
#endif // CONFIG_BTDM_CTRL_HLI
#endif // XTensa

View File

@@ -1,507 +0,0 @@
/*-------------------------------------------------------------------------
NeoPixel library helper functions for Esp32.
A BIG thanks to Andreas Merkle for the investigation and implementation of
a workaround to the GCC bug that drops method attributes from template methods
Written by Michael C. Miller.
I invest time and resources providing this open source code,
please support me by donating (see https://github.com/Makuna/NeoPixelBus)
-------------------------------------------------------------------------
This file is part of the Makuna/NeoPixelBus library.
NeoPixelBus 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 3 of
the License, or (at your option) any later version.
NeoPixelBus 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 NeoPixel. If not, see
<http://www.gnu.org/licenses/>.
-------------------------------------------------------------------------*/
#include <Arduino.h>
#if defined(ARDUINO_ARCH_ESP32)
#include <algorithm>
#include "esp_idf_version.h"
#include "NeoEsp32RmtHIMethod.h"
#include "soc/soc.h"
#include "soc/rmt_reg.h"
#ifdef __riscv
#include "riscv/interrupt.h"
#endif
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0)
#include "hal/rmt_ll.h"
#else
/* Shims for older ESP-IDF v3; we can safely assume original ESP32 */
#include "soc/rmt_struct.h"
// Selected RMT API functions borrowed from ESP-IDF v4.4.8
// components/hal/esp32/include/hal/rmt_ll.h
// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
__attribute__((always_inline))
static inline void rmt_ll_tx_reset_pointer(rmt_dev_t *dev, uint32_t channel)
{
dev->conf_ch[channel].conf1.mem_rd_rst = 1;
dev->conf_ch[channel].conf1.mem_rd_rst = 0;
}
__attribute__((always_inline))
static inline void rmt_ll_tx_start(rmt_dev_t *dev, uint32_t channel)
{
dev->conf_ch[channel].conf1.tx_start = 1;
}
__attribute__((always_inline))
static inline void rmt_ll_tx_stop(rmt_dev_t *dev, uint32_t channel)
{
RMTMEM.chan[channel].data32[0].val = 0;
dev->conf_ch[channel].conf1.tx_start = 0;
dev->conf_ch[channel].conf1.mem_rd_rst = 1;
dev->conf_ch[channel].conf1.mem_rd_rst = 0;
}
__attribute__((always_inline))
static inline void rmt_ll_tx_enable_pingpong(rmt_dev_t *dev, uint32_t channel, bool enable)
{
dev->apb_conf.mem_tx_wrap_en = enable;
}
__attribute__((always_inline))
static inline void rmt_ll_tx_enable_loop(rmt_dev_t *dev, uint32_t channel, bool enable)
{
dev->conf_ch[channel].conf1.tx_conti_mode = enable;
}
__attribute__((always_inline))
static inline uint32_t rmt_ll_tx_get_channel_status(rmt_dev_t *dev, uint32_t channel)
{
return dev->status_ch[channel];
}
__attribute__((always_inline))
static inline void rmt_ll_tx_set_limit(rmt_dev_t *dev, uint32_t channel, uint32_t limit)
{
dev->tx_lim_ch[channel].limit = limit;
}
__attribute__((always_inline))
static inline void rmt_ll_enable_interrupt(rmt_dev_t *dev, uint32_t mask, bool enable)
{
if (enable) {
dev->int_ena.val |= mask;
} else {
dev->int_ena.val &= ~mask;
}
}
__attribute__((always_inline))
static inline void rmt_ll_enable_tx_end_interrupt(rmt_dev_t *dev, uint32_t channel, bool enable)
{
dev->int_ena.val &= ~(1 << (channel * 3));
dev->int_ena.val |= (enable << (channel * 3));
}
__attribute__((always_inline))
static inline void rmt_ll_enable_tx_err_interrupt(rmt_dev_t *dev, uint32_t channel, bool enable)
{
dev->int_ena.val &= ~(1 << (channel * 3 + 2));
dev->int_ena.val |= (enable << (channel * 3 + 2));
}
__attribute__((always_inline))
static inline void rmt_ll_enable_tx_thres_interrupt(rmt_dev_t *dev, uint32_t channel, bool enable)
{
dev->int_ena.val &= ~(1 << (channel + 24));
dev->int_ena.val |= (enable << (channel + 24));
}
__attribute__((always_inline))
static inline void rmt_ll_clear_tx_end_interrupt(rmt_dev_t *dev, uint32_t channel)
{
dev->int_clr.val = (1 << (channel * 3));
}
__attribute__((always_inline))
static inline void rmt_ll_clear_tx_err_interrupt(rmt_dev_t *dev, uint32_t channel)
{
dev->int_clr.val = (1 << (channel * 3 + 2));
}
__attribute__((always_inline))
static inline void rmt_ll_clear_tx_thres_interrupt(rmt_dev_t *dev, uint32_t channel)
{
dev->int_clr.val = (1 << (channel + 24));
}
__attribute__((always_inline))
static inline uint32_t rmt_ll_get_tx_thres_interrupt_status(rmt_dev_t *dev)
{
uint32_t status = dev->int_st.val;
return (status & 0xFF000000) >> 24;
}
#endif
// *********************************
// Select method for binding interrupt
//
// - If the Bluetooth driver has registered a high-level interrupt, piggyback on that API
// - If we're on a modern core, allocate the interrupt with the API (old cores are bugged)
// - Otherwise use the low-level hardware API to manually bind the interrupt
#if defined(CONFIG_BTDM_CTRL_HLI)
// Espressif's bluetooth driver offers a helpful sharing layer; bring in the interrupt management calls
#include "hal/interrupt_controller_hal.h"
extern "C" esp_err_t hli_intr_register(intr_handler_t handler, void* arg, uint32_t intr_reg, uint32_t intr_mask);
#else /* !CONFIG_BTDM_CTRL_HLI*/
// Declare the our high-priority ISR handler
extern "C" void ld_include_hli_vectors_rmt(); // an object with an address, but no space
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3)
#include "soc/periph_defs.h"
#endif
// Select level flag
#if defined(__riscv)
// RISCV chips don't block interrupts while scheduling; all we need to do is be higher than the WiFi ISR
#define INT_LEVEL_FLAG ESP_INTR_FLAG_LEVEL3
#elif defined(CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5)
#define INT_LEVEL_FLAG ESP_INTR_FLAG_LEVEL4
#else
#define INT_LEVEL_FLAG ESP_INTR_FLAG_LEVEL5
#endif
// ESP-IDF v3 cannot enable high priority interrupts through the API at all;
// and ESP-IDF v4 on XTensa cannot enable Level 5 due to incorrect interrupt descriptor tables
#if !defined(__XTENSA__) || (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)) || ((ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) && CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5))
#define NEOESP32_RMT_CAN_USE_INTR_ALLOC
// XTensa cores require the assembly bridge
#ifdef __XTENSA__
#define HI_IRQ_HANDLER nullptr
#define HI_IRQ_HANDLER_ARG ld_include_hli_vectors_rmt
#else
#define HI_IRQ_HANDLER NeoEsp32RmtMethodIsr
#define HI_IRQ_HANDLER_ARG nullptr
#endif
#else
/* !CONFIG_BTDM_CTRL_HLI && !NEOESP32_RMT_CAN_USE_INTR_ALLOC */
// This is the index of the LV5 interrupt vector - see interrupt descriptor table in idf components/hal/esp32/interrupt_descriptor_table.c
#define ESP32_LV5_IRQ_INDEX 26
#endif /* NEOESP32_RMT_CAN_USE_INTR_ALLOC */
#endif /* CONFIG_BTDM_CTRL_HLI */
// RMT driver implementation
struct NeoEsp32RmtHIChannelState {
uint32_t rmtBit0, rmtBit1;
uint32_t resetDuration;
const byte* txDataStart; // data array
const byte* txDataEnd; // one past end
const byte* txDataCurrent; // current location
size_t rmtOffset;
};
// Global variables
#if defined(NEOESP32_RMT_CAN_USE_INTR_ALLOC)
static intr_handle_t isrHandle = nullptr;
#endif
static NeoEsp32RmtHIChannelState** driverState = nullptr;
constexpr size_t rmtBatchSize = RMT_MEM_ITEM_NUM / 2;
// Fill the RMT buffer memory
// This is implemented using many arguments instead of passing the structure object to ensure we do only one lookup
// All the arguments are passed in registers, so they don't need to be looked up again
static void IRAM_ATTR RmtFillBuffer(uint8_t channel, const byte** src_ptr, const byte* end, uint32_t bit0, uint32_t bit1, size_t* offset_ptr, size_t reserve) {
// We assume that (rmtToWrite % 8) == 0
size_t rmtToWrite = rmtBatchSize - reserve;
rmt_item32_t* dest =(rmt_item32_t*) &RMTMEM.chan[channel].data32[*offset_ptr + reserve]; // write directly in to RMT memory
const byte* psrc = *src_ptr;
*offset_ptr ^= rmtBatchSize;
if (psrc != end) {
while (rmtToWrite > 0) {
uint8_t data = *psrc;
for (uint8_t bit = 0; bit < 8; bit++)
{
dest->val = (data & 0x80) ? bit1 : bit0;
dest++;
data <<= 1;
}
rmtToWrite -= 8;
psrc++;
if (psrc == end) {
break;
}
}
*src_ptr = psrc;
}
if (rmtToWrite > 0) {
// Add end event
rmt_item32_t bit0_val = {{.val = bit0 }};
*dest = rmt_item32_t {{{ .duration0 = 0, .level0 = bit0_val.level1, .duration1 = 0, .level1 = bit0_val.level1 }}};
}
}
static void IRAM_ATTR RmtStartWrite(uint8_t channel, NeoEsp32RmtHIChannelState& state) {
// Reset context state
state.rmtOffset = 0;
// Fill the first part of the buffer with a reset event
// FUTURE: we could do timing analysis with the last interrupt on this channel
// Use 8 words to stay aligned with the buffer fill logic
rmt_item32_t bit0_val = {{.val = state.rmtBit0 }};
rmt_item32_t fill = {{{ .duration0 = 100, .level0 = bit0_val.level1, .duration1 = 100, .level1 = bit0_val.level1 }}};
rmt_item32_t* dest = (rmt_item32_t*) &RMTMEM.chan[channel].data32[0];
for (auto i = 0; i < 7; ++i) dest[i] = fill;
fill.duration1 = state.resetDuration > 1400 ? (state.resetDuration - 1400) : 100;
dest[7] = fill;
// Fill the remaining buffer with real data
RmtFillBuffer(channel, &state.txDataCurrent, state.txDataEnd, state.rmtBit0, state.rmtBit1, &state.rmtOffset, 8);
RmtFillBuffer(channel, &state.txDataCurrent, state.txDataEnd, state.rmtBit0, state.rmtBit1, &state.rmtOffset, 0);
// Start operation
rmt_ll_clear_tx_thres_interrupt(&RMT, channel);
rmt_ll_tx_reset_pointer(&RMT, channel);
rmt_ll_tx_start(&RMT, channel);
}
extern "C" void IRAM_ATTR NeoEsp32RmtMethodIsr(void *arg) {
// Tx threshold interrupt
uint32_t status = rmt_ll_get_tx_thres_interrupt_status(&RMT);
while (status) {
uint8_t channel = __builtin_ffs(status) - 1;
if (driverState[channel]) {
// Normal case
NeoEsp32RmtHIChannelState& state = *driverState[channel];
RmtFillBuffer(channel, &state.txDataCurrent, state.txDataEnd, state.rmtBit0, state.rmtBit1, &state.rmtOffset, 0);
} else {
// Danger - another driver got invoked?
rmt_ll_tx_stop(&RMT, channel);
}
rmt_ll_clear_tx_thres_interrupt(&RMT, channel);
status = rmt_ll_get_tx_thres_interrupt_status(&RMT);
}
};
// Wrapper around the register analysis defines
// For all currently supported chips, this is constant for all channels; but this is not true of *all* ESP32
static inline bool _RmtStatusIsTransmitting(rmt_channel_t channel, uint32_t status) {
uint32_t v;
switch(channel) {
#ifdef RMT_STATE_CH0
case 0: v = (status >> RMT_STATE_CH0_S) & RMT_STATE_CH0_V; break;
#endif
#ifdef RMT_STATE_CH1
case 1: v = (status >> RMT_STATE_CH1_S) & RMT_STATE_CH1_V; break;
#endif
#ifdef RMT_STATE_CH2
case 2: v = (status >> RMT_STATE_CH2_S) & RMT_STATE_CH2_V; break;
#endif
#ifdef RMT_STATE_CH3
case 3: v = (status >> RMT_STATE_CH3_S) & RMT_STATE_CH3_V; break;
#endif
#ifdef RMT_STATE_CH4
case 4: v = (status >> RMT_STATE_CH4_S) & RMT_STATE_CH4_V; break;
#endif
#ifdef RMT_STATE_CH5
case 5: v = (status >> RMT_STATE_CH5_S) & RMT_STATE_CH5_V; break;
#endif
#ifdef RMT_STATE_CH6
case 6: v = (status >> RMT_STATE_CH6_S) & RMT_STATE_CH6_V; break;
#endif
#ifdef RMT_STATE_CH7
case 7: v = (status >> RMT_STATE_CH7_S) & RMT_STATE_CH7_V; break;
#endif
default: v = 0;
}
return v != 0;
}
esp_err_t NeoEsp32RmtHiMethodDriver::Install(rmt_channel_t channel, uint32_t rmtBit0, uint32_t rmtBit1, uint32_t reset) {
// Validate channel number
if (channel >= RMT_CHANNEL_MAX) {
return ESP_ERR_INVALID_ARG;
}
esp_err_t err = ESP_OK;
if (!driverState) {
// First time init
driverState = reinterpret_cast<NeoEsp32RmtHIChannelState**>(heap_caps_calloc(RMT_CHANNEL_MAX, sizeof(NeoEsp32RmtHIChannelState*), MALLOC_CAP_INTERNAL));
if (!driverState) return ESP_ERR_NO_MEM;
// Ensure all interrupts are cleared before binding
RMT.int_ena.val = 0;
RMT.int_clr.val = 0xFFFFFFFF;
// Bind interrupt handler
#if defined(CONFIG_BTDM_CTRL_HLI)
// Bluetooth driver has taken the empty high-priority interrupt. Fortunately, it allows us to
// hook up another handler.
err = hli_intr_register(NeoEsp32RmtMethodIsr, nullptr, (uintptr_t) &RMT.int_st, 0xFF000000);
// 25 is the magic number of the bluetooth ISR on ESP32 - see soc/soc.h.
intr_matrix_set(cpu_hal_get_core_id(), ETS_RMT_INTR_SOURCE, 25);
intr_cntrl_ll_enable_interrupts(1<<25);
#elif defined(NEOESP32_RMT_CAN_USE_INTR_ALLOC)
// Use the platform code to allocate the interrupt
// If we need the additional assembly bridge, we pass it as the "arg" to the IDF so it gets linked in
err = esp_intr_alloc(ETS_RMT_INTR_SOURCE, INT_LEVEL_FLAG | ESP_INTR_FLAG_IRAM, HI_IRQ_HANDLER, (void*) HI_IRQ_HANDLER_ARG, &isrHandle);
//err = ESP_ERR_NOT_FINISHED;
#else
// Broken IDF API does not allow us to reserve the interrupt; do it manually
static volatile const void* __attribute__((used)) pleaseLinkAssembly = (void*) ld_include_hli_vectors_rmt;
intr_matrix_set(xPortGetCoreID(), ETS_RMT_INTR_SOURCE, ESP32_LV5_IRQ_INDEX);
ESP_INTR_ENABLE(ESP32_LV5_IRQ_INDEX);
#endif
if (err != ESP_OK) {
heap_caps_free(driverState);
driverState = nullptr;
return err;
}
}
if (driverState[channel] != nullptr) {
return ESP_ERR_INVALID_STATE; // already in use
}
NeoEsp32RmtHIChannelState* state = reinterpret_cast<NeoEsp32RmtHIChannelState*>(heap_caps_calloc(1, sizeof(NeoEsp32RmtHIChannelState), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT));
if (state == nullptr) {
return ESP_ERR_NO_MEM;
}
// Store timing information
state->rmtBit0 = rmtBit0;
state->rmtBit1 = rmtBit1;
state->resetDuration = reset;
// Initialize hardware
rmt_ll_tx_stop(&RMT, channel);
rmt_ll_tx_reset_pointer(&RMT, channel);
rmt_ll_enable_tx_err_interrupt(&RMT, channel, false);
rmt_ll_enable_tx_end_interrupt(&RMT, channel, false);
rmt_ll_enable_tx_thres_interrupt(&RMT, channel, false);
rmt_ll_clear_tx_err_interrupt(&RMT, channel);
rmt_ll_clear_tx_end_interrupt(&RMT, channel);
rmt_ll_clear_tx_thres_interrupt(&RMT, channel);
rmt_ll_tx_enable_loop(&RMT, channel, false);
rmt_ll_tx_enable_pingpong(&RMT, channel, true);
rmt_ll_tx_set_limit(&RMT, channel, rmtBatchSize);
driverState[channel] = state;
rmt_ll_enable_tx_thres_interrupt(&RMT, channel, true);
return err;
}
esp_err_t NeoEsp32RmtHiMethodDriver::Uninstall(rmt_channel_t channel) {
if ((channel >= RMT_CHANNEL_MAX) || !driverState || !driverState[channel]) return ESP_ERR_INVALID_ARG;
NeoEsp32RmtHIChannelState* state = driverState[channel];
WaitForTxDone(channel, 10000 / portTICK_PERIOD_MS);
// Done or not, we're out of here
rmt_ll_tx_stop(&RMT, channel);
rmt_ll_enable_tx_thres_interrupt(&RMT, channel, false);
driverState[channel] = nullptr;
heap_caps_free(state);
#if !defined(CONFIG_BTDM_CTRL_HLI) /* Cannot unbind from bluetooth ISR */
// Turn off the driver ISR and release global state if none are left
for (uint8_t channelIndex = 0; channelIndex < RMT_CHANNEL_MAX; ++channelIndex) {
if (driverState[channelIndex]) return ESP_OK; // done
}
#if defined(NEOESP32_RMT_CAN_USE_INTR_ALLOC)
esp_intr_free(isrHandle);
#else
ESP_INTR_DISABLE(ESP32_LV5_IRQ_INDEX);
#endif
heap_caps_free(driverState);
driverState = nullptr;
#endif /* !defined(CONFIG_BTDM_CTRL_HLI) */
return ESP_OK;
}
esp_err_t NeoEsp32RmtHiMethodDriver::Write(rmt_channel_t channel, const uint8_t *src, size_t src_size) {
if ((channel >= RMT_CHANNEL_MAX) || !driverState || !driverState[channel]) return ESP_ERR_INVALID_ARG;
NeoEsp32RmtHIChannelState& state = *driverState[channel];
esp_err_t result = WaitForTxDone(channel, 10000 / portTICK_PERIOD_MS);
if (result == ESP_OK) {
state.txDataStart = src;
state.txDataCurrent = src;
state.txDataEnd = src + src_size;
RmtStartWrite(channel, state);
}
return result;
}
esp_err_t NeoEsp32RmtHiMethodDriver::WaitForTxDone(rmt_channel_t channel, TickType_t wait_time) {
if ((channel >= RMT_CHANNEL_MAX) || !driverState || !driverState[channel]) return ESP_ERR_INVALID_ARG;
NeoEsp32RmtHIChannelState& state = *driverState[channel];
// yield-wait until wait_time
esp_err_t rv = ESP_OK;
uint32_t status;
while(1) {
status = rmt_ll_tx_get_channel_status(&RMT, channel);
if (!_RmtStatusIsTransmitting(channel, status)) break;
if (wait_time == 0) { rv = ESP_ERR_TIMEOUT; break; };
TickType_t sleep = std::min(wait_time, (TickType_t) 5);
vTaskDelay(sleep);
wait_time -= sleep;
};
return rv;
}
#endif

View File

@@ -1,79 +0,0 @@
Import('env')
import subprocess
import re
def get_github_repo():
"""Extract GitHub repository name from git remote URL.
Uses the remote that the current branch tracks, falling back to 'origin'.
This handles cases where repositories have multiple remotes or where the
main remote is not named 'origin'.
Returns:
str: Repository name in 'owner/repo' format for GitHub repos,
'unknown' for non-GitHub repos, missing git CLI, or any errors.
"""
try:
remote_name = 'origin' # Default fallback
# Try to get the remote for the current branch
try:
# Get current branch name
branch_result = subprocess.run(['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
capture_output=True, text=True, check=True)
current_branch = branch_result.stdout.strip()
# Get the remote for the current branch
remote_result = subprocess.run(['git', 'config', f'branch.{current_branch}.remote'],
capture_output=True, text=True, check=True)
tracked_remote = remote_result.stdout.strip()
# Use the tracked remote if we found one
if tracked_remote:
remote_name = tracked_remote
except subprocess.CalledProcessError:
# If branch config lookup fails, continue with 'origin' as fallback
pass
# Get the remote URL for the determined remote
result = subprocess.run(['git', 'remote', 'get-url', remote_name],
capture_output=True, text=True, check=True)
remote_url = result.stdout.strip()
# Check if it's a GitHub URL
if 'github.com' not in remote_url.lower():
return 'unknown'
# Parse GitHub URL patterns:
# https://github.com/owner/repo.git
# git@github.com:owner/repo.git
# https://github.com/owner/repo
# Remove .git suffix if present
if remote_url.endswith('.git'):
remote_url = remote_url[:-4]
# Handle HTTPS URLs
https_match = re.search(r'github\.com/([^/]+/[^/]+)', remote_url, re.IGNORECASE)
if https_match:
return https_match.group(1)
# Handle SSH URLs
ssh_match = re.search(r'github\.com:([^/]+/[^/]+)', remote_url, re.IGNORECASE)
if ssh_match:
return ssh_match.group(1)
return 'unknown'
except FileNotFoundError:
# Git CLI is not installed or not in PATH
return 'unknown'
except subprocess.CalledProcessError:
# Git command failed (e.g., not a git repo, no remote, etc.)
return 'unknown'
except Exception:
# Any other unexpected error
return 'unknown'
repo = get_github_repo()
env.Append(BUILD_FLAGS=[f'-DWLED_REPO=\\"{repo}\\"'])

View File

@@ -111,7 +111,6 @@ ldscript_4m1m = eagle.flash.4m1m.ld
[scripts_defaults]
extra_scripts =
pre:pio-scripts/set_version.py
pre:pio-scripts/set_repo.py
post:pio-scripts/output_bins.py
post:pio-scripts/strip-floats.py
pre:pio-scripts/user_config_copy.py
@@ -142,6 +141,7 @@ lib_deps =
fastled/FastLED @ 3.6.0
IRremoteESP8266 @ 2.8.2
makuna/NeoPixelBus @ 2.8.3
#https://github.com/makuna/NeoPixelBus.git#CoreShaderBeta
https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.4.2
marvinroger/AsyncMqttClient @ 0.9.0
# for I2C interface
@@ -450,7 +450,7 @@ board_build.partitions = ${esp32.large_partitions}
board_upload.flash_size = 8MB
board_upload.maximum_size = 8388608
; board_build.f_flash = 80000000L
board_build.flash_mode = dio
; board_build.flash_mode = qio
[env:esp32dev_16M]
board = esp32dev

View File

@@ -28,6 +28,7 @@ lib_deps = ${esp8266.lib_deps}
; robtillaart/SHT85@~0.3.3
; ;gmag11/QuickESPNow @ ~0.7.0 # will also load QuickDebug
; https://github.com/blazoncek/QuickESPNow.git#optional-debug ;; exludes debug library
; ${esp32.AR_lib_deps} ;; needed for USERMOD_AUDIOREACTIVE
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags}
@@ -139,6 +140,8 @@ build_flags = ${common.build_flags} ${esp8266.build_flags}
; -D PIR_SENSOR_MAX_SENSORS=2 # max allowable sensors (uses OR logic for triggering)
;
; Use Audioreactive usermod and configure I2S microphone
; ${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 DMTYPE=1 # 0-analog/disabled, 1-I2S generic, 2-ES7243, 3-SPH0645, 4-I2S+mclk, 5-I2S PDM
; -D I2S_SDPIN=36
@@ -177,7 +180,7 @@ build_flags = ${common.build_flags} ${esp8266.build_flags}
;
; 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
;
;
; 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
;
@@ -238,7 +241,9 @@ lib_deps = ${esp8266.lib_deps}
extends = env:esp32dev # we want to extend the existing esp32dev environment (and define only updated options)
board = esp32dev
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}
${esp32.AR_lib_deps} ;; needed for USERMOD_AUDIOREACTIVE
monitor_filters = esp32_exception_decoder
board_build.f_flash = 80000000L
board_build.flash_mode = qio
@@ -250,7 +255,9 @@ board_build.flash_mode = qio
extends = esp32_idf_V4 # based on newer "esp-idf V4" platform environment
board = esp32dev
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}
${esp32.AR_lib_deps} ;; needed for USERMOD_AUDIOREACTIVE
monitor_filters = esp32_exception_decoder
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
@@ -368,9 +375,11 @@ build_flags = ${common.build_flags} ${esp32.build_flags}
-D USERMOD_DALLASTEMPERATURE
-D USERMOD_FOUR_LINE_DISPLAY
-D TEMPERATURE_PIN=23
${esp32.AR_build_flags} ;; includes USERMOD_AUDIOREACTIVE
lib_deps = ${esp32.lib_deps}
OneWire@~2.3.5 ;; needed for USERMOD_DALLASTEMPERATURE
olikraus/U8g2 @ ^2.28.8 ;; needed for USERMOD_FOUR_LINE_DISPLAY
${esp32.AR_lib_deps} ;; needed for USERMOD_AUDIOREACTIVE
board_build.partitions = ${esp32.default_partitions}
[env:esp32_pico-D4]
@@ -382,11 +391,13 @@ build_flags = ${common.build_flags} ${esp32.build_flags}
-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}
${esp32.AR_lib_deps} ;; needed for USERMOD_AUDIOREACTIVE
board_build.partitions = ${esp32.default_partitions}
board_build.f_flash = 80000000L
@@ -514,7 +525,6 @@ build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_DISABLE_BROWNOU
-D USER_SETUP_LOADED
monitor_filters = esp32_exception_decoder
# ------------------------------------------------------------------------------
# Usermod examples
# ------------------------------------------------------------------------------
@@ -525,87 +535,3 @@ extends = env:esp32dev
build_flags = ${env:esp32dev.build_flags} -D USERMOD_RF433
lib_deps = ${env:esp32dev.lib_deps}
sui77/rc-switch @ 2.6.4
# ------------------------------------------------------------------------------
# Hub75 examples
# ------------------------------------------------------------------------------
[env:esp32dev_hub75]
board = esp32dev
upload_speed = 921600
platform = ${esp32_idf_V4.platform}
platform_packages =
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags}
-D WLED_RELEASE_NAME=\"ESP32_hub75\"
-D WLED_ENABLE_HUB75MATRIX -D NO_GFX
-D WLED_DEBUG_BUS
; -D WLED_DEBUG
lib_deps = ${esp32_idf_V4.lib_deps}
https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA.git#3.0.11
monitor_filters = esp32_exception_decoder
board_build.partitions = ${esp32.default_partitions}
board_build.flash_mode = dio
[env:esp32dev_hub75_forum_pinout]
extends = env:esp32dev_hub75
build_flags = ${common.build_flags}
-D WLED_RELEASE_NAME=\"ESP32_hub75_forum_pinout\"
-D WLED_ENABLE_HUB75MATRIX -D NO_GFX
-D ESP32_FORUM_PINOUT ;; enable for SmartMatrix default pins
-D WLED_DEBUG_BUS
; -D WLED_DEBUG
[env:adafruit_matrixportal_esp32s3]
; ESP32-S3 processor, 8 MB flash, 2 MB of PSRAM, dedicated driver pins for HUB75
board = adafruit_matrixportal_esp32s3
platform = ${esp32s3.platform}
platform_packages =
upload_speed = 921600
build_unflags = ${common.build_unflags}
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")
-DBOARD_HAS_PSRAM
-DLOLIN_WIFI_FIX ; seems to work much better with this
-D WLED_WATCHDOG_TIMEOUT=0
-D WLED_ENABLE_HUB75MATRIX -D NO_GFX
-D S3_LCD_DIV_NUM=20 ;; Attempt to fix wifi performance issue when panel active with S3 chips
-D ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3
-D WLED_DEBUG_BUS
lib_deps = ${esp32s3.lib_deps}
https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA.git#aa28e2a ;; S3_LCD_DIV_NUM fix
board_build.partitions = ${esp32.default_partitions}
board_build.f_flash = 80000000L
board_build.flash_mode = qio
monitor_filters = esp32_exception_decoder
[env:esp32S3_PSRAM_HUB75]
;; MOONHUB HUB75 adapter board
board = lilygo-t7-s3
platform = ${esp32s3.platform}
platform_packages =
upload_speed = 921600
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"esp32S3_16MB_PSRAM_HUB75\"
-DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
-DBOARD_HAS_PSRAM
-DLOLIN_WIFI_FIX ; seems to work much better with this
-D WLED_WATCHDOG_TIMEOUT=0
-D WLED_ENABLE_HUB75MATRIX -D NO_GFX
-D S3_LCD_DIV_NUM=20 ;; Attempt to fix wifi performance issue when panel active with S3 chips
-D MOONHUB_S3_PINOUT ;; HUB75 pinout
-D WLED_DEBUG_BUS
lib_deps = ${esp32s3.lib_deps}
https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA.git#aa28e2a ;; S3_LCD_DIV_NUM fix
board_build.partitions = ${esp32.default_partitions}
board_build.f_flash = 80000000L
board_build.flash_mode = qio
monitor_filters = esp32_exception_decoder

View File

@@ -143,7 +143,7 @@ async function writeHtmlGzipped(sourceFile, resultFile, page) {
console.info("Minified and compressed " + sourceFile + " from " + originalLength + " to " + result.length + " bytes");
const array = hexdump(result);
let src = singleHeader;
src += `const uint16_t PAGE_${page}_length = ${result.length};\n`;
src += `const uint16_t PAGE_${page}_L = ${result.length};\n`;
src += `const uint8_t PAGE_${page}[] PROGMEM = {\n${array}\n};\n\n`;
console.info("Writing " + resultFile);
fs.writeFileSync(resultFile, src);
@@ -244,22 +244,9 @@ if (isAlreadyBuilt("wled00/data") && process.argv[2] !== '--force' && process.ar
writeHtmlGzipped("wled00/data/index.htm", "wled00/html_ui.h", 'index');
writeHtmlGzipped("wled00/data/pixart/pixart.htm", "wled00/html_pixart.h", 'pixart');
//writeHtmlGzipped("wled00/data/cpal/cpal.htm", "wled00/html_cpal.h", 'cpal');
writeHtmlGzipped("wled00/data/cpal/cpal.htm", "wled00/html_cpal.h", 'cpal');
writeHtmlGzipped("wled00/data/pxmagic/pxmagic.htm", "wled00/html_pxmagic.h", 'pxmagic');
writeChunks(
"wled00/data/cpal",
[
{
file: "cpal.htm",
name: "PAGE_cpal",
method: "gzip",
filter: "html-minify"
}
],
"wled00/html_cpal.h"
);
writeChunks(
"wled00/data",
[

View File

@@ -28,78 +28,28 @@ log() {
fi
}
# Fetch a URL to a destination file, validating status codes.
# Usage: fetch "<url>" "<dest or empty>" "200 404"
fetch() {
local url="$1"
local dest="$2"
local accepted="${3:-200}"
# Generic curl handler function
curl_handler() {
local command="$1"
local hostname="$2"
# If no dest given, just discard body
local out
if [ -n "$dest" ]; then
# Write to ".tmp" files first, then move when success, to ensure we don't write partial files
out="${dest}.tmp"
response=$($command -w "%{http_code}" -o /dev/null)
curl_exit_code=$?
if [ "$response" -ge 200 ] && [ "$response" -lt 300 ]; then
return 0
elif [ $curl_exit_code -ne 0 ]; then
log "ERROR" "$RED" "Connection error during request to $hostname (curl exit code: $curl_exit_code)."
return 1
elif [ "$response" -ge 400 ]; then
log "ERROR" "$RED" "Server error during request to $hostname (HTTP status code: $response)."
return 2
else
out="/dev/null"
log "ERROR" "$RED" "Unexpected response from $hostname (HTTP status code: $response)."
return 3
fi
response=$(curl --connect-timeout 5 --max-time 30 -s -w "%{http_code}" -o "$out" "$url")
local curl_exit_code=$?
if [ $curl_exit_code -ne 0 ]; then
[ -n "$dest" ] && rm -f "$out"
log "ERROR" "$RED" "Connection error during request to $url (curl exit code: $curl_exit_code)."
return 1
fi
for code in $accepted; do
if [ "$response" = "$code" ]; then
# Accepted; only persist body for 2xx responses
if [ -n "$dest" ]; then
if [[ "$response" =~ ^2 ]]; then
mv "$out" "$dest"
else
rm -f "$out"
fi
fi
return 0
fi
done
# not accepted
[ -n "$dest" ] && rm -f "$out"
log "ERROR" "$RED" "Unexpected response from $url (HTTP $response)."
return 2
}
# POST a file to a URL, validating status codes.
# Usage: post_file "<url>" "<file>" "200"
post_file() {
local url="$1"
local file="$2"
local accepted="${3:-200}"
response=$(curl --connect-timeout 5 --max-time 300 -s -w "%{http_code}" -o /dev/null -X POST -F "file=@$file" "$url")
local curl_exit_code=$?
if [ $curl_exit_code -ne 0 ]; then
log "ERROR" "$RED" "Connection error during POST to $url (curl exit code: $curl_exit_code)."
return 1
fi
for code in $accepted; do
if [ "$response" -eq "$code" ]; then
return 0
fi
done
log "ERROR" "$RED" "Unexpected response from $url (HTTP $response)."
return 2
}
# Print help message
show_help() {
cat << EOF
@@ -159,27 +109,33 @@ backup_one() {
local address="$2"
local port="$3"
log "INFO" "$YELLOW" "Backing up device config/presets/ir: $hostname ($address:$port)"
log "INFO" "$YELLOW" "Backing up device config/presets: $hostname ($address:$port)"
mkdir -p "$backup_dir"
local file_prefix="${backup_dir}/${hostname}"
local cfg_url="http://$address:$port/cfg.json"
local presets_url="http://$address:$port/presets.json"
local cfg_dest="${backup_dir}/${hostname}.cfg.json"
local presets_dest="${backup_dir}/${hostname}.presets.json"
if ! fetch "http://$address:$port/cfg.json" "${file_prefix}.cfg.json"; then
# Write to ".tmp" files first, then move when success, to ensure we don't write partial files
local curl_command_cfg="curl -s "$cfg_url" -o "$cfg_dest.tmp""
local curl_command_presets="curl -s "$presets_url" -o "$presets_dest.tmp""
if ! curl_handler "$curl_command_cfg" "$hostname"; then
log "ERROR" "$RED" "Failed to backup configuration for $hostname"
rm -f "$cfg_dest.tmp"
return 1
fi
if ! fetch "http://$address:$port/presets.json" "${file_prefix}.presets.json"; then
if ! curl_handler "$curl_command_presets" "$hostname"; then
log "ERROR" "$RED" "Failed to backup presets for $hostname"
rm -f "$presets_dest.tmp"
return 1
fi
# ir.json is optional
if ! fetch "http://$address:$port/ir.json" "${file_prefix}.ir.json" "200 404"; then
log "ERROR" "$RED" "Failed to backup ir configs for $hostname"
fi
fi
mv "$cfg_dest.tmp" "$cfg_dest"
mv "$presets_dest.tmp" "$presets_dest"
log "INFO" "$GREEN" "Successfully backed up config and presets for $hostname"
return 0
}
@@ -194,8 +150,9 @@ update_one() {
log "INFO" "$YELLOW" "Starting firmware update for device: $hostname ($address:$port)"
local url="http://$address:$port/update"
local curl_command="curl -s -X POST -F "file=@$firmware" "$url""
if ! post_file "$url" "$firmware" "200"; then
if ! curl_handler "$curl_command" "$hostname"; then
log "ERROR" "$RED" "Failed to update firmware for $hostname"
return 1
fi

View File

@@ -224,8 +224,8 @@ void FFTcode(void * parameter)
DEBUGSR_PRINT("FFT started on core: "); DEBUGSR_PRINTLN(xPortGetCoreID());
// allocate FFT buffers on first call
if (vReal == nullptr) vReal = (float*) calloc(samplesFFT, sizeof(float));
if (vImag == nullptr) vImag = (float*) calloc(samplesFFT, sizeof(float));
if (vReal == nullptr) vReal = (float*) calloc(sizeof(float), samplesFFT);
if (vImag == nullptr) vImag = (float*) calloc(sizeof(float), samplesFFT);
if ((vReal == nullptr) || (vImag == nullptr)) {
// something went wrong
if (vReal) free(vReal); vReal = nullptr;
@@ -1981,7 +1981,7 @@ void AudioReactive::createAudioPalettes(void) {
if (palettes) return;
DEBUG_PRINTLN(F("Adding audio palettes."));
for (int i=0; i<MAX_PALETTES; i++)
if (customPalettes.size() < WLED_MAX_CUSTOM_PALETTES) {
if (customPalettes.size() < 10) {
customPalettes.push_back(CRGBPalette16(CRGB(BLACK)));
palettes++;
DEBUG_PRINTLN(palettes);

View File

@@ -54,11 +54,7 @@ class RgbRotaryEncoderUsermod : public Usermod
void initLedBus()
{
// Initialize all pins to the sentinel value first…
byte _pins[OUTPUT_MAX_PINS];
std::fill(std::begin(_pins), std::end(_pins), 255);
// …then set only the LED pin
_pins[0] = static_cast<byte>(ledIo);
byte _pins[5] = {(byte)ledIo, 255, 255, 255, 255};
BusConfig busCfg = BusConfig(TYPE_WS2812_RGB, _pins, 0, numLeds, COL_ORDER_GRB, false, 0);
ledBus = new BusDigital(busCfg, WLED_MAX_BUSSES - 1);

View File

@@ -1,504 +1,4 @@
# Usermod user FX
This usermod is a common place to put various users WLED effects. It lets you load your own custom effects or bring back deprecated ones—without touching core WLED source code.
This Usermod is a common place to put various user's LED effects.
Multiple Effects can be specified inside this single usermod, as we will illustrate below. You will be able to define them with custom names, sliders, etc. as with any other Effect.
* [How The Usermod Works](./README.md#how-the-usermod-works)
* [Basic Syntax for WLED Effect Creation](./README.md#basic-syntax-for-wled-effect-creation)
* [Understanding 2D WLED Effects](./README.md#understanding-2d-wled-effects)
* [The Metadata String](./README.md#the-metadata-string)
* [Understanding 1D WLED Effects](./README.md#understanding-1d-wled-effects)
* [Combining Multiple Effects in this Usermod](./README.md#combining-multiple-effects-in-this-usermod)
* [Compiling](./README.md#compiling)
* [Change Log](./README.md#change-log)
* [Contact Us](./README.md#contact-us)
## How The Usermod Works
The `user_fx.cpp` file can be broken down into four main parts:
* **static effect definition** - This is a static LED setting that is displayed if an effect fails to initialize.
* **User FX function definition(s)** - This area is where you place the FX code for all of the custom effects you want to use. This mainly includes the FX code and the static variable containing the [metadata string](https://kno.wled.ge/interfaces/json-api/#effect-metadata).
* **Usermod Class definition(s)** - The class definition defines the blueprint from which all your custom Effects (or any usermod, for that matter) are created.
* **Usermod registration** - All usermods have to be registered so that they are able to be compiled into your binary.
We will go into greater detail on how custom effects work in the usermod and how to go about creating your own in the section below.
## Basic Syntax for WLED Effect Creation
WLED effects generally follow a certain procedure for their operation:
1. Determine dimension of segment
2. Calculate new state if needed
3. Implement a loop that calculates color for each pixel and sets it using `SEGMENT.setPixelColor()`
4. The function is called at current frame rate.
Below are some helpful variables and functions to know as you start your journey towards WLED effect creation:
| Syntax Element | Size | Description |
| :---------------------------------------------- | :----- | :---------- |
| [`SEGMENT.speed / intensity / custom1 / custom2`](https://github.com/wled/WLED/blob/75f6de9dc29fc7da5f301fc1388ada228dcb3b6e/wled00/FX.h#L450) | 8-bit | These read-only variables help you control aspects of your custom effect using the UI sliders. You can edit these variables through the UI sliders when WLED is running your effect. (These variables can be controlled by the API as well.) Note that while `SEGMENT.intensity` through `SEGMENT.custom2` are 8-bit variables, `SEGMENT.custom3` is actually 5-bit. The other three bits are used by the boolean parameters `SEGMENT.check1` through `SEGMENT.check3` and are bit-packed to conserve data size and memory. |
| [`SEGMENT.custom3`](https://github.com/wled/WLED/blob/75f6de9dc29fc7da5f301fc1388ada228dcb3b6e/wled00/FX.h#L454) | 5-bit | Another optional UI slider for custom effect control. While `SEGMENT.speed` through `SEGMENT.custom2` are 8-bit variables, `SEGMENT.custom3` is actually 5-bit. |
| [`SEGMENT.check1 / check2 / check3`](https://github.com/wled/WLED/blob/75f6de9dc29fc7da5f301fc1388ada228dcb3b6e/wled00/FX.h#L455) | 1-bit | These variables are boolean parameters which show up as checkbox options in the User Interface. They are bit-packed along with `SEGMENT.custom3` to conserve data size and memory. |
| [`SEGENV.aux0 / aux1`](https://github.com/wled/WLED/blob/75f6de9dc29fc7da5f301fc1388ada228dcb3b6e/wled00/FX.h#L467) | 16-bit | These are state variables that persists between function calls, and they are free to be overwritten by the user for any use case. |
| [`SEGENV.step`](https://github.com/wled/WLED/blob/75f6de9dc29fc7da5f301fc1388ada228dcb3b6e/wled00/FX.h#L465) | 32-bit | This is a timestamp variable that contains the last update time. It is initially set during effect initialization to 0, and then it updates with the elapsed time after each frame runs. |
| [`SEGENV.call`](https://github.com/wled/WLED/blob/75f6de9dc29fc7da5f301fc1388ada228dcb3b6e/wled00/FX.h#L466) | 32-bit | A counter for how many times this effect function has been invoked since it started. |
| [`strip.now`](https://github.com/wled/WLED/blob/main/wled00/FX.h) | 32-bit | Current timestamp in milliseconds. (Equivalent to `millis()`, but use `strip.now()` instead.) `strip.now` respects the timebase, which can be used to advance or reset effects in a preset. This can be useful to sync multiple segments. |
| [`SEGLEN / SEG_W / SEG_H`](https://github.com/wled/WLED/blob/75f6de9dc29fc7da5f301fc1388ada228dcb3b6e/wled00/FX.h#L116) | 16-bit | These variables are macros that help define the length and width of your LED strip/matrix segment. |
| [`SEGPALETTE`](https://github.com/wled/WLED/blob/75f6de9dc29fc7da5f301fc1388ada228dcb3b6e/wled00/FX.h#L115) | --- | Macro that gets the currently selected palette for the currently processing segment. |
| [`hw_random8()`](https://github.com/wled/WLED/blob/7b0075d3754fa883fc1bbc9fbbe82aa23a9b97b8/wled00/fcn_declare.h#L548) | 8-bit | One of several functions that generates a random integer. (All of the "hw_" functions are similar to the FastLED library's random functions, but in WLED they use true hardware-based randomness instead of a pseudo random number. In short, they are better and faster.) |
| [`SEGCOLOR(x)`](https://github.com/wled/WLED/blob/75f6de9dc29fc7da5f301fc1388ada228dcb3b6e/wled00/FX.h#L114) | 32-bit | Macro that gets user-selected colors from UI, where x is an integer 1, 2, or 3 for primary, secondary, and tertiary colors, respectively. |
| [`SEGMENT.setPixelColor`](https://github.com/WLED/WLED/blob/75f6de9dc29fc7da5f301fc1388ada228dcb3b6e/wled00/FX_fcn.cpp) / [`setPixelColorXY`](https://github.com/WLED/WLED/blob/75f6de9dc29fc7da5f301fc1388ada228dcb3b6e/wled00/FX_2Dfcn.cpp) | 32-bit | Function that paints one pixel. `setPixelColor` is 1D; `setPixelColorXY` expects `(x, y)` and an RGBW color value. |
| [`SEGMENT.color_wheel()`](https://github.com/wled/WLED/blob/75f6de9dc29fc7da5f301fc1388ada228dcb3b6e/wled00/FX_fcn.cpp#L1092) | 32-bit | Input 0255 to get a color. Transitions r→g→b→r. In HSV terms, `pos` is H. Note: only returns palette color unless the Default palette is selected. |
| [`SEGMENT.color_from_palette()`](https://github.com/wled/WLED/blob/75f6de9dc29fc7da5f301fc1388ada228dcb3b6e/wled00/FX_fcn.cpp#L1093) | 32-bit | Gets a single color from the currently selected palette for a segment. (This function which should be favoured over `ColorFromPalette()` because this function returns an RGBW color with white from the `SEGCOLOR` passed, while also respecting the setting for palette wrapping. On the other hand, `ColorFromPalette()` simply gets the RGB palette color.) |
| [`fade_out()`](https://github.com/wled/WLED/blob/75f6de9dc29fc7da5f301fc1388ada228dcb3b6e/wled00/FX_fcn.cpp#L1012) | --- | fade out function, higher rate = quicker fade. fading is highly dependent on frame rate (higher frame rates, faster fading). each frame will fade at max 9% or as little as 0.8%. |
| [`fadeToBlackBy()`](https://github.com/wled/WLED/blob/75f6de9dc29fc7da5f301fc1388ada228dcb3b6e/wled00/FX_fcn.cpp#L1043) | --- | can be used to fade all pixels to black. |
| [`fadeToSecondaryBy()`](https://github.com/wled/WLED/blob/75f6de9dc29fc7da5f301fc1388ada228dcb3b6e/wled00/FX_fcn.cpp#L1043) | --- | fades all pixels to secondary color. |
| [`move()`](https://github.com/WLED/WLED/blob/75f6de9dc29fc7da5f301fc1388ada228dcb3b6e/wled00/FX_fcn.cpp) | --- | Moves/shifts pixels in the desired direction. |
| [`blur / blur2d`](https://github.com/wled/WLED/blob/75f6de9dc29fc7da5f301fc1388ada228dcb3b6e/wled00/FX_fcn.cpp#L1053) | --- | Blurs all pixels for the desired segment. Blur also has the boolean option `smear`, which, when activated, does not fade the blurred pixel(s). |
You will see how these syntax elements work in the examples below.
## Understanding 2D WLED Effects
In this section we give some advice to those who are new to WLED Effect creation. We will illustrate how to load in multiple Effects using this single usermod, and we will do a deep dive into the anatomy of a 1D Effect as well as a 2D Effect.
(Special thanks to @mryndzionek for offering this "Diffusion Fire" 2D Effect for this tutorial.)
### Imports
The first line of the code imports the [wled.h](https://github.com/wled/WLED/blob/main/wled00/wled.h) file into this module. Importing `wled.h` brings all of the variables, files, and functions listed in the table above (and more) into your custom effect for you to use.
```cpp
#include "wled.h"
```
### Static Effect Definition
The next code block is the `mode_static` definition. This is usually left as `SEGMENT.fill(SEGCOLOR(0));` to leave all pixels off if the effect fails to load, but in theory one could use this as a 'fallback effect' to take on a different behavior, such as displaying some other color instead of leaving the pixels off.
### User Effect Definitions
Pre-loaded in this template is an example 2D Effect called "Diffusion Fire". (This is the name that would be shown in the UI once the binary is compiled and run on your device, as defined in the metadata string.)
The effect starts off by checking to see if the segment that the effect is being applied to is a 2D Matrix, and if it is not, then it returns the static effect which displays no pattern:
```cpp
if (!strip.isMatrix || !SEGMENT.is2D())
return mode_static(); // not a 2D set-up
```
The next code block contains several constant variable definitions which essentially serve to extract the dimensions of the user's 2D matrix and allow WLED to interpret the matrix as a 1D coordinate system (WLED must do this for all 2D animations):
```cpp
const int cols = SEG_W;
const int rows = SEG_H;
const auto XY = [&](int x, int y) { return x + y * cols; };
```
* The first line assigns the number of columns (width) in the active segment to cols.
* SEG_W is a macro defined in WLED that expands to SEGMENT.width(). This value is the width of your 2D matrix segment, used to traverse the matrix correctly.
* Next, we assign the number of rows (height) in the segment to rows.
* SEG_H is a macro for SEGMENT.height(). Combined with cols, this allows pixel addressing in 2D (x, y) space.
* The third line declares a lambda function named `XY` to map (x, y) matrix coordinates into a 1D index in the LED array. This assumes row-major order (left to right, top to bottom).
* This lambda helps with mapping a local 1D array to a 2D one.
The next lines of code further the setup process by defining variables that allow the effect's settings to be configurable using the UI sliders (or alternatively, through API calls):
```cpp
const uint8_t refresh_hz = map(SEGMENT.speed, 0, 255, 20, 80);
const unsigned refresh_ms = 1000 / refresh_hz;
const int16_t diffusion = map(SEGMENT.custom1, 0, 255, 0, 100);
const uint8_t spark_rate = SEGMENT.intensity;
const uint8_t turbulence = SEGMENT.custom2;
```
* The first line maps the SEGMENT.speed (user-controllable parameter from 0255) to a value between 20 and 80 Hz.
* This determines how often the effect should refresh per second (Higher speed = more frames per second).
* Next we convert refresh rate from Hz to milliseconds. (Its easier to schedule animation updates in WLED using elapsed time in milliseconds.)
* This value is used to time when to update the effect.
* The third line utilizes the `custom1` control (0255 range, usually exposed via sliders) to define the diffusion rate, mapped to 0100.
* This controls how much "heat" spreads to neighboring pixels — more diffusion = smoother flame spread.
* Next we assign `SEGMENT.intensity` (user input 0255) to a variable named `spark_rate`.
* This controls how frequently new "spark" pixels appear at the bottom of the matrix.
* A higher value means more frequent ignition of flame points.
* The final line stores the user-defined `custom2` value to a variable called `turbulence`.
* This is used to introduce randomness in spark generation or flow — more turbulence means more chaotic behavior.
Next we will look at some lines of code that handle memory allocation and effect initialization:
```cpp
unsigned dataSize = cols * rows; // SEGLEN (virtual length) is equivalent to vWidth()*vHeight() for 2D
```
* This part calculates how much memory we need to represent per-pixel state.
* `cols * rows` or `(or SEGLEN)` returns the total number of pixels in the current segment.
* This fire effect models heat values per pixel (not just colors), so we need persistent storage — one uint8_t per pixel — for the entire effect.
> **_NOTE:_** Virtual lengths `vWidth()` and `vHeight()` will be evaluated differently based on your own custom effect, and based on what other settings are active. For example: If you have an LED strip of length = 60 and you enable grouping = 2, then the virtual length will be 30, so the FX will render 30 pixels instead of 60. This is also true for mirroring or adding gaps--it halves the size. For a 1D strip mapped to 2D, the virtual length depends on selected mode. Keep these things in mind during your custom effect's creation.
```cpp
if (!SEGENV.allocateData(dataSize))
return mode_static(); // allocation failed
```
* Upon the first call, this section allocates a persistent data buffer tied to the segment environment (`SEGENV.data`). All subsequent calls simply ensure that the data is still valid.
* The syntax `SEGENV.allocateData(n)` requests a buffer of size n bytes (1 byte per pixel here).
* If allocation fails (e.g., out of memory), it returns false, and the effect cant proceed.
* It calls previously defined `mode_static()` fallback effect, which just fills the segment with a static color. We need to do this because WLED needs a fail-safe behavior if a custom effect can't run properly due to memory constraints.
The next lines of code clear the LEDs and initialize timing:
```cpp
if (SEGENV.call == 0) {
SEGMENT.fill(BLACK);
SEGENV.step = 0;
}
```
* The first line checks whether this is the first time the effect is being run; `SEGENV.call` is a counter for how many times this effect function has been invoked since it started.
* If `SEGENV.call` equals 0 (which it does on the very first call, making it useful for initialization), then it clears the LED segment by filling it with black (turns off all LEDs).
* This gives a clean starting point for the fire animation.
* It also initializes `SEGENV.step`, a timing marker, to 0. This value is later used as a timestamp to control when the next animation frame should occur (based on elapsed time).
The next block of code is where the animation update logic starts to kick in:
```cpp
if ((strip.now - SEGENV.step) >= refresh_ms) {
uint8_t tmp_row[cols]; // Keep for ≤~1 KiB; otherwise consider heap or reuse SEGENV.data as scratch.
SEGENV.step = strip.now;
// scroll up
for (unsigned y = 1; y < rows; y++)
for (unsigned x = 0; x < cols; x++) {
unsigned src = XY(x, y);
unsigned dst = XY(x, y - 1);
SEGENV.data[dst] = SEGENV.data[src];
}
```
* The first line checks if it's time to update the effect frame. `strip.now` is the current timestamp in milliseconds; `SEGENV.step` is the last update time (set during initialization or previous frame). `refresh_ms` is how long to wait between frames, computed earlier based on SEGMENT.speed.
* The conditional statement in the first line of code ensures the effect updates on a fixed interval — e.g., every 20 ms for 50 Hz.
* The second line of code declares a temporary row buffer for intermediate diffusion results that is one byte per column (horizontal position), so this buffer holds one row's worth of heat values.
* You'll see later that it writes results here before updating `SEGENV.data`.
* Note: this is allocated on the stack each frame. Keep such VLAs ≤ ~1 KiB; for larger sizes, prefer a buffer in `SEGENV.data`.
> **_IMPORTANT NOTE:_** Creating variablelength arrays (VLAs) is nonstandard C++, but this practice is used throughout WLED and works in practice. But be aware that VLAs live on the stack, which is limited. If the array scales with segment length (1D), it can overflow the stack and crash. Keep VLAs ≲ ~1 KiB; an array with 4000 LEDs is ~4 KiB and will likely crash. Its worse with `uint16_t`. Anything larger than ~1 KiB should go into `SEGENV.data`, which has a higher limit.
Now we get to the spark generation portion, where new bursts of heat appear at the bottom of the matrix:
```cpp
if (hw_random8() > turbulence) {
// create new sparks at bottom row
for (unsigned x = 0; x < cols; x++) {
uint8_t p = hw_random8();
if (p < spark_rate) {
unsigned dst = XY(x, rows - 1);
SEGENV.data[dst] = 255;
}
}
}
```
* The first line randomizes whether we even attempt to spawn sparks this frame.
* `hw_random8()` gives a random number between 0255 using a fast hardware RNG.
* `turbulence` is a user-controlled parameter (SEGMENT.custom2, set earlier).
* Higher turbulence means this block is less likely to run (because `hw_random8()` is less likely to exceed a high threshold).
* This adds randomness to when sparks appear — simulating natural flicker and chaotic fire.
* The next line loops over all columns in the bottom row (row `rows - 1`).
* Another random number, `p`, is used to probabilistically decide whether a spark appears at this (x, `rows-1`) position.
* Next is a conditional statement. The lower spark_rate is, the fewer sparks will appear.
* `spark_rate` comes from `SEGMENT.intensity` (0255).
* High intensity means more frequent ignition.
* `dst` calculates the destination index in the bottom row at column x.
* The final line here sets the heat at this pixel to maximum (255).
* This simulates a fresh burst of flame, which will diffuse and move upward over time in subsequent frames.
Next we reach the first part of the core of the fire simulation, which is diffusion (how heat spreads to neighboring pixels):
```cpp
// diffuse
for (unsigned y = 0; y < rows; y++) {
for (unsigned x = 0; x < cols; x++) {
unsigned v = SEGENV.data[XY(x, y)];
if (x > 0) {
v += SEGENV.data[XY(x - 1, y)];
}
if (x < (cols - 1)) {
v += SEGENV.data[XY(x + 1, y)];
}
tmp_row[x] = min(255, (int)(v * 100 / (300 + diffusion)));
}
```
* This block of code starts by looping over each row from top to bottom. (We will do diffusion for each pixel row.)
* Next we start an inner loop which iterates across each column in the current row.
* Starting with the current heat value of pixel (x, y) assigned `v`:
* if theres a pixel to the left, add its heat to the total.
* If theres a pixel to the right, add its heat as well.
* So essentially, what the two `if` statements accomplish is: `v = center + left + right`.
* The final line of code applies diffusion smoothing:
* The denominator controls how much the neighboring heat contributes. `300 + diffusion` means that with higher diffusion, you get more smoothing (since the sum is divided more).
* The `v * 100` scales things before dividing (preserving some dynamic range).
* `min(255, ...)` clamps the result to 8-bit range.
* This entire line of code stores the smoothed heat into the temporary row buffer.
After calculating tmp_row, we now handle rendering the pixels by updating the actual segment data and turning 'heat' into visible colors:
```cpp
for (unsigned x = 0; x < cols; x++) {
SEGENV.data[XY(x, y)] = tmp_row[x];
if (SEGMENT.check1) {
uint32_t color = SEGMENT.color_from_palette(tmp_row[x], true, false, 0);
SEGMENT.setPixelColorXY(x, y, color);
} else {
uint32_t base = SEGCOLOR(0);
SEGMENT.setPixelColorXY(x, y, color_fade(base, tmp_row[x]));
}
}
}
```
* This next loop starts iterating over each row from top to bottom. (We're now doing this for color-rendering for each pixel row.)
* Next we update the main segment data with the smoothed value for this pixel.
* The if statement creates a conditional rendering path — the user can toggle this. If `check1` is enabled in the effect metadata, we use a color palette to display the flame.
* The next line converts the heat value (`tmp_row[x]`) into a `color` from the current palette with 255 brightness, and no wrapping in palette lookup.
* This creates rich gradient flames (e.g., yellow → red → black).
* Finally we set the rendered color for the pixel (x, y).
* This repeats for each pixel in each row.
* If palette use is disabled, we fallback to fading a base color.
* `SEGCOLOR(0)` gets the first user-selected color for the segment.
* The final line of code fades that base color according to the heat value (acts as brightness multiplier).
The final piece of this custom effect returns the frame time:
```cpp
}
return FRAMETIME;
}
```
* The first bracket closes the earlier `if ((strip.now - SEGENV.step) >= refresh_ms)` block.
* It ensures that the fire simulation (scrolling, sparking, diffusion, rendering) only runs when enough time has passed since the last update.
* returning the frame time tells WLED how soon this effect wants to be called again.
* `FRAMETIME` is a predefined macro in WLED, typically set to ~16ms, corresponding to ~60 FPS (frames per second).
* Even though the effect logic itself controls when to update based on refresh_ms, WLED will still call this function at roughly FRAMETIME intervals to check whether an update is needed.
* ⚠️ Important: Because the actual frame logic is gated by strip.now - SEGENV.step, returning FRAMETIME here doesnt cause excessive updates — it just keeps the engine responsive. **Also note that an Effect should ALWAYS return FRAMETIME. Not doing so can cause glitches.**
* The final bracket closes the `mode_diffusionfire()` function itself.
### The Metadata String
At the end of every effect is an important line of code called the **metadata string**.
It defines how the effect is to be interacted with in the UI:
```cpp
static const char _data_FX_MODE_DIFFUSIONFIRE[] PROGMEM = "Diffusion Fire@!,Spark rate,Diffusion Speed,Turbulence,,Use palette;;Color;;2;pal=35";
```
This metadata string is passed into `strip.addEffect()` and parsed by WLED to determine how your effect appears and behaves in the UI.
The string follows the syntax of `<Effect Parameters>;<Colors>;<Palette>;<Flags>;<Defaults>`, where Effect Parameters are specified by a comma-separated list.
The values for Effect Parameters will always follow the convention in the table below:
| Parameter | Default tooltip label |
| :-------- | :-------------------- |
| sx | Effect Speed |
| ix | Effect Intensity |
| c1 | Custom 1 |
| c2 | Custom 2 |
| c3 | Custom 3 |
| o1 | Checkbox 1 |
| o2 | Checkbox 2 |
| o3 | Checkbox 3 |
Using this info, lets split the Metadata string above into logical sections:
| Syntax Element | Description |
| :---------------------------------------------- | :---------- |
| "Diffusion Fire@! | Name. (The @ symbol marks the end of the Effect Name, and the beginning of the Parameter String elements.) |
| !, | Use default UI entry; for the first space, this will automatically create a slider for Speed |
| Spark rate, Diffusion Speed, Turbulence, | UI sliders for Spark Rate, Diffusion Speed, and Turbulence. Defining slider 2 as "Spark Rate" overwrites the default value of Intensity. |
| (blank), | unused (empty field with not even a space) |
| Use palette; | This occupies the spot for the 6th effect parameter, which automatically makes this a checkbox argument `o1` called Use palette in the UI. When this is enabled, the effect uses `SEGMENT.color_from_palette(...)` (RGBW-aware, respects wrap), otherwise it fades from `SEGCOLOR(0)`. The first semicolon marks the end of the Effect Parameters and the beginning of the `Colors` parameter. |
| Color; | Custom color field `(SEGCOLOR(0))` |
| (blank); | Empty means the effect does not allow Palettes to be selected by the user. But used in conjunction with the checkbox argument, palette use can be turned on/off by the user. |
| 2; | Flag specifying that the effect requires a 2D matrix setup |
| pal=35" | Default Palette ID. this is the setting that the effect starts up with. |
More information on metadata strings can be found [here](https://kno.wled.ge/interfaces/json-api/#effect-metadata).
## Understanding 1D WLED Effects
Next, we will look at a 1D WLED effect called `Sinelon`. This one is an especially interesting example because it shows how a single effect function can be used to create several different selectable effects in the UI.
We will break this effect down step by step.
(This effect was originally one of the FastLED example effects; more information on FastLED can be found [here](https://fastled.io/).)
```cpp
static uint16_t sinelon_base(bool dual, bool rainbow=false) {
```
* The first line of code defines `sinelon base` as static helper function. This is how all effects are initially defined.
* Notice that it has some optional flags; these parameters will allow us to easily define the effect in different ways in the UI.
```cpp
if (SEGLEN <= 1) return mode_static();
```
* If segment length ≤ 1, theres nothing to animate. Just show static mode.
The line of code helps create the "Fade Out" Trail:
```cpp
SEGMENT.fade_out(SEGMENT.intensity);
```
* Gradually dims all LEDs each frame using SEGMENT.intensity as fade amount.
* Creates the trailing "comet" effect by leaving a fading path behind the moving dot.
Next, the effect computes some position information for the actively changing pixel, and the rest of the pixels as well:
```cpp
unsigned pos = beatsin16_t(SEGMENT.speed/10, 0, SEGLEN-1);
if (SEGENV.call == 0) SEGENV.aux0 = pos;
```
* Calculates a sine-based oscillation to move the dot smoothly back and forth.
* `beatsin16_t` is an improved version of FastLEDs beatsin16 function, generating smooth oscillations
* SEGMENT.speed / 10: affects oscillation speed. Higher = faster.
* 0: minimum position.
* SEGLEN-1: maximum position.
* On first call `(SEGENV.call == 0)`, stores initial position in `SEGENV.aux0`. (`SEGENV.aux0` is a temporary state variable to keep track of last position.)
The next lines of code help determine the colors to be used:
```cpp
uint32_t color1 = SEGMENT.color_from_palette(pos, true, false, 0);
uint32_t color2 = SEGCOLOR(2);
```
* `color1`: main moving dot color, chosen from palette using the current position as index.
* `color2`: secondary color from user-configured color slot 2.
The next part takes into account the optional argument for if a Rainbow colored palette is in use:
```cpp
if (rainbow) {
color1 = SEGMENT.color_wheel((pos & 0x07) * 32);
}
```
* If `rainbow` is true, override color1 using a rainbow wheel, producing rainbow cycling colors.
* `(pos & 0x07) * 32` ensures the color changes gradually with position.
```cpp
SEGMENT.setPixelColor(pos, color1);
```
* Lights up the computed position with the selected color.
The next line takes into account another one of the optional arguments for the effect to potentially handle dual mirrored dots which create the animation:
```cpp
if (dual) {
if (!color2) color2 = SEGMENT.color_from_palette(pos, true, false, 0);
if (rainbow) color2 = color1; // share rainbow color
SEGMENT.setPixelColor(SEGLEN-1-pos, color2);
}
```
* If dual is true:
* Uses `color2` for mirrored dot on opposite side.
* If `color2` is not set (0), fallback to same palette color as `color1`.
* In `rainbow` mode, force both dots to share the rainbow color.
* Sets pixel at `SEGLEN-1-pos` to `color2`.
This final part of the effect function will fill in the 'trailing' pixels to complete the animation:
```cpp
if (SEGENV.aux0 < pos) {
for (unsigned i = SEGENV.aux0; i < pos ; i++) {
SEGMENT.setPixelColor(i, color1);
if (dual) SEGMENT.setPixelColor(SEGLEN-1-i, color2);
}
} else {
for (unsigned i = SEGENV.aux0; i > pos ; i--) {
SEGMENT.setPixelColor(i, color1);
if (dual) SEGMENT.setPixelColor(SEGLEN-1-i, color2);
}
}
SEGENV.aux0 = pos;
}
```
* The first line checks if current position has changed since last frame. (Prevents holes if the dot moves quickly and "skips" pixels.) If the position has changed, then it will implement the logic to update the rest of the pixels.
* Fills in all pixels between previous position (SEGENV.aux0) and new position (pos) to ensure smooth continuous trail.
* Works in both directions: Forward (if new pos > old pos), and Backward (if new pos < old pos).
* Updates `SEGENV.aux0` to current position at the end.
Finally, we return the `FRAMETIME`, as with all effect functions:
```cpp
return FRAMETIME;
}
```
* Returns `FRAMETIME` constant to set effect update rate (usually ~16 ms).
The last part of this effect has the Wrapper functions for different Sinelon modes.
Notice that there are three different modes that we can define from the single effect definition by leveraging the arguments in the function:
```cpp
uint16_t mode_sinelon(void) {
return sinelon_base(false);
}
// Calls sinelon_base with dual = false and rainbow = false
uint16_t mode_sinelon_dual(void) {
return sinelon_base(true);
}
// Calls sinelon_base with dual = true and rainbow = false
uint16_t mode_sinelon_rainbow(void) {
return sinelon_base(false, true);
}
// Calls sinelon_base with dual = false and rainbow = true
```
And then the last part defines the metadata strings for each effect to specify how it will be portrayed in the UI:
```cpp
static const char _data_FX_MODE_SINELON[] PROGMEM = "Sinelon@!,Trail;!,!,!;!";
static const char _data_FX_MODE_SINELON_DUAL[] PROGMEM = "Sinelon Dual@!,Trail;!,!,!;!";
static const char _data_FX_MODE_SINELON_RAINBOW[] PROGMEM = "Sinelon Rainbow@!,Trail;,,!;!";
```
Refer to the section above for guidance on understanding metadata strings.
### The UserFxUsermod Class
The `UserFxUsermod` class registers the `mode_diffusionfire` effect with WLED. This section starts right after the effect function and metadata string, and is responsible for making the effect usable in the WLED interface:
```cpp
class UserFxUsermod : public Usermod {
private:
public:
void setup() override {
strip.addEffect(255, &mode_diffusionfire, _data_FX_MODE_DIFFUSIONFIRE);
////////////////////////////////////////
// add your effect function(s) here //
////////////////////////////////////////
// use id=255 for all custom user FX (the final id is assigned when adding the effect)
// strip.addEffect(255, &mode_your_effect, _data_FX_MODE_YOUR_EFFECT);
// strip.addEffect(255, &mode_your_effect2, _data_FX_MODE_YOUR_EFFECT2);
// strip.addEffect(255, &mode_your_effect3, _data_FX_MODE_YOUR_EFFECT3);
}
void loop() override {} // nothing to do in the loop
uint16_t getId() override { return USERMOD_ID_USER_FX; }
};
```
* The first line declares a new class called UserFxUsermod. It inherits from `Usermod`, which is the base class WLED uses for any pluggable user-defined modules.
* This makes UserFxUsermod a valid WLED extension that can hook into `setup()`, `loop()`, and other lifecycle events.
* The `void setup()` function runs once when WLED initializes the usermod.
* It's where you should register your effects, initialize hardware, or do any other setup logic.
* `override` ensures that this matches the Usermod base class definition.
* The `strip.addEffect` line is an important one that registers the custom effect so WLED knows about it.
* 255: Temporary ID — WLED will assign a unique ID automatically. (**Create all custom effects with the 255 ID.**)
* `&mode_diffusionfire`: Pointer to the effect function.
* `_data_FX_MODE_DIFFUSIONFIRE`: Metadata string stored in PROGMEM, describing the effect name and UI fields (like sliders).
* After this, your custom effect shows up in the WLED effects list.
* The `loop()` function remains empty because this usermod doesnt need to do anything continuously. WLED still calls this every main loop, but nothing is done here.
* If your usermod had to respond to input or update state, you'd do it here.
* The last part returns a unique ID constant used to identify this usermod.
* USERMOD_ID_USER_FX is defined in [const.h](https://github.com/wled/WLED/blob/main/wled00/const.h). WLED uses this for tracking, debugging, or referencing usermods internally.
The final part of this file handles instantiation and initialization:
```cpp
static UserFxUsermod user_fx;
REGISTER_USERMOD(user_fx);
```
* The first line creates a single, global instance of your usermod class.
* The last line is a macro that tells WLED: “This is a valid usermod — load it during startup.”
* WLED adds it to the list of active usermods, calls `setup()` and `loop()`, and lets it interact with the system.
## Combining Multiple Effects in this Usermod
So now let's say that you wanted add the effects "Diffusion Fire" and "Sinelon" through this same Usermod file:
* Navigate to [the code for Sinelon](https://github.com/wled/WLED/blob/7b0075d3754fa883fc1bbc9fbbe82aa23a9b97b8/wled00/FX.cpp#L3110).
* Copy this code, and place it below the metadata string for Diffusion Fire. Be sure to get the metadata string as well--and to name it something different than what's already inside the core WLED code. (Refer to the metadata String section above for more information.)
* Register the effect using the `addEffect` function in the Usermod class.
* Compile the code!
## Compiling
Compiling WLED yourself is beyond the scope of this tutorial, but [the complete guide to compiling WLED can be found here](https://kno.wled.ge/advanced/compiling-wled/), on the official WLED documentation website.
## Change Log
### Version 1.0.0
* First version of the custom effect creation guide
## Contact Us
This custom effect tutorial guide is still in development.
If you have suggestions on what should be added, or if you've found any parts of this guide which seem incorrect, feel free to reach out [here](mailto:aregis1992@gmail.com) and help us improve this guide for future creators.

View File

@@ -27,7 +27,7 @@ static uint16_t mode_diffusionfire(void) {
const uint8_t spark_rate = SEGMENT.intensity;
const uint8_t turbulence = SEGMENT.custom2;
unsigned dataSize = cols * rows; // SEGLEN (virtual length) is equivalent to vWidth()*vHeight() for 2D
unsigned dataSize = SEGMENT.length(); // allocate persistent data for heat value for each pixel
if (!SEGENV.allocateData(dataSize))
return mode_static(); // allocation failed
@@ -37,7 +37,6 @@ unsigned dataSize = cols * rows; // SEGLEN (virtual length) is equivalent to vW
}
if ((strip.now - SEGENV.step) >= refresh_ms) {
// Keep for ≤~1 KiB; otherwise consider heap or reuse SEGENV.data as scratch.
uint8_t tmp_row[cols];
SEGENV.step = strip.now;
// scroll up
@@ -45,7 +44,7 @@ unsigned dataSize = cols * rows; // SEGLEN (virtual length) is equivalent to vW
for (unsigned x = 0; x < cols; x++) {
unsigned src = XY(x, y);
unsigned dst = XY(x, y - 1);
SEGENV.data[dst] = SEGENV.data[src];
SEGMENT.data[dst] = SEGMENT.data[src];
}
if (hw_random8() > turbulence) {
@@ -54,7 +53,7 @@ unsigned dataSize = cols * rows; // SEGLEN (virtual length) is equivalent to vW
uint8_t p = hw_random8();
if (p < spark_rate) {
unsigned dst = XY(x, rows - 1);
SEGENV.data[dst] = 255;
SEGMENT.data[dst] = 255;
}
}
}
@@ -62,24 +61,24 @@ unsigned dataSize = cols * rows; // SEGLEN (virtual length) is equivalent to vW
// diffuse
for (unsigned y = 0; y < rows; y++) {
for (unsigned x = 0; x < cols; x++) {
unsigned v = SEGENV.data[XY(x, y)];
unsigned v = SEGMENT.data[XY(x, y)];
if (x > 0) {
v += SEGENV.data[XY(x - 1, y)];
v += SEGMENT.data[XY(x - 1, y)];
}
if (x < (cols - 1)) {
v += SEGENV.data[XY(x + 1, y)];
v += SEGMENT.data[XY(x + 1, y)];
}
tmp_row[x] = min(255, (int)(v * 100 / (300 + diffusion)));
}
for (unsigned x = 0; x < cols; x++) {
SEGENV.data[XY(x, y)] = tmp_row[x];
SEGMENT.data[XY(x, y)] = tmp_row[x];
if (SEGMENT.check1) {
uint32_t color = SEGMENT.color_from_palette(tmp_row[x], true, false, 0);
uint32_t color = ColorFromPalette(SEGPALETTE, tmp_row[x], 255, LINEARBLEND_NOWRAP);
SEGMENT.setPixelColorXY(x, y, color);
} else {
uint32_t base = SEGCOLOR(0);
SEGMENT.setPixelColorXY(x, y, color_fade(base, tmp_row[x]));
uint32_t color = SEGCOLOR(0);
SEGMENT.setPixelColorXY(x, y, color_fade(color, tmp_row[x]));
}
}
}

View File

@@ -135,8 +135,7 @@ uint16_t mode_copy_segment(void) {
SEGMENT.fadeToBlackBy(5); // fade out
return FRAMETIME;
}
Segment& sourcesegment = strip.getSegment(sourceid);
Segment sourcesegment = strip.getSegment(sourceid);
if (sourcesegment.isActive()) {
uint32_t sourcecolor;
uint32_t destcolor;
@@ -678,7 +677,7 @@ uint16_t mode_twinkle(void) {
SEGENV.step = it;
}
uint16_t PRNG16 = SEGENV.aux1;
unsigned PRNG16 = SEGENV.aux1;
for (unsigned i = 0; i < SEGENV.aux0; i++)
{
@@ -1715,8 +1714,8 @@ static const char _data_FX_MODE_TRICOLOR_WIPE[] PROGMEM = "Tri Wipe@!;1,2,3;!";
* Modified by Aircoookie
*/
uint16_t mode_tricolor_fade(void) {
uint16_t counter = strip.now * ((SEGMENT.speed >> 3) +1);
uint32_t prog = (counter * 768) >> 16;
unsigned counter = strip.now * ((SEGMENT.speed >> 3) +1);
uint16_t prog = (counter * 768) >> 16;
uint32_t color1 = 0, color2 = 0;
unsigned stage = 0;
@@ -2607,11 +2606,9 @@ static CRGB twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat)
// This is like 'triwave8', which produces a
// symmetrical up-and-down triangle sawtooth waveform, except that this
// function produces a triangle wave with a faster attack and a slower decay
if (cat) { //twinklecat, variant where the leds instantly turn on and fade off
if (cat) //twinklecat, variant where the leds instantly turn on
{
bright = 255 - ph;
if (SEGMENT.check2) { //reverse checkbox, reverses the leds to fade on and instantly turn off
bright = ph;
}
} else { //vanilla twinklefox
if (ph < 86) {
bright = ph * 3;
@@ -2719,7 +2716,7 @@ uint16_t mode_twinklecat()
{
return twinklefox_base(true);
}
static const char _data_FX_MODE_TWINKLECAT[] PROGMEM = "Twinklecat@!,Twinkle rate,,,,Cool,Reverse;!,!;!";
static const char _data_FX_MODE_TWINKLECAT[] PROGMEM = "Twinklecat@!,Twinkle rate,,,,Cool;!,!;!";
uint16_t mode_halloween_eyes()
@@ -4878,78 +4875,6 @@ uint16_t mode_FlowStripe(void) {
} // mode_FlowStripe()
static const char _data_FX_MODE_FLOWSTRIPE[] PROGMEM = "Flow Stripe@Hue speed,Effect speed;;!;pal=11";
/*
Shimmer effect: moves a gradient with optional modulators across the strip at a given interval, up to 60 seconds
It can be used as an overlay to other effects or standalone
by DedeHai (Damian Schneider), based on idea from @Charming-Lime (#4905)
*/
uint16_t mode_shimmer() {
if(!SEGENV.allocateData(sizeof(uint32_t))) { return mode_static(); }
uint32_t* lastTime = reinterpret_cast<uint32_t*>(SEGENV.data);
uint32_t radius = (SEGMENT.custom1 * SEGLEN >> 7) + 1; // [1, 2*SEGLEN+1] pixels
uint32_t traversalDistance = (SEGLEN + 2 * radius) << 8; // total subpixels to cross, 1 pixel = 256 subpixels
uint32_t traversalTime = 200 + (255 - SEGMENT.speed) * 80; // [200, 20600] ms
uint32_t speed = ((traversalDistance << 5) / traversalTime); // subpixels/512ms
int32_t position = static_cast<int32_t>(SEGENV.step); // current position in subpixels
uint16_t inputstate = (uint16_t(SEGMENT.intensity) << 8) | uint16_t(SEGMENT.custom1); // current user input state
// init
if (SEGENV.call == 0 || inputstate != SEGENV.aux1) {
position = -(radius << 8);
SEGENV.aux0 = 0; // aux0 is pause timer
*lastTime = strip.now;
SEGENV.aux1 = inputstate; // save user input state
}
if(SEGMENT.speed) {
uint32_t deltaTime = (strip.now - *lastTime) & 0x7F; // clamp to 127ms to avoid overflows. note: speed*deltaTime can still overflow for segments > ~10k pixels
*lastTime = strip.now;
if (SEGENV.aux0 > 0) {
SEGENV.aux0 = (SEGENV.aux0 > deltaTime) ? SEGENV.aux0 - deltaTime : 0;
} else {
// calculate movement step and update position
int32_t step = 1 + ((speed * deltaTime) >> 5); // subpixels moved this frame. note >>5 as speed is in subpixels/512ms
position += step;
int endposition = (SEGLEN + radius) << 8;
if (position > endposition) {
SEGENV.aux0 = SEGMENT.intensity * 236; // [0, 60180] ms pause
if(SEGMENT.check3) SEGENV.aux0 = hw_random(SEGENV.aux0 + 1000); // randomise interval, +1 second to affect low intensity values
position = -(radius << 8); // reset to start position (out of frame)
}
SEGENV.step = (uint32_t)position; // save back
}
if (SEGMENT.check2)
position = (SEGLEN << 8) - position; // invert position (and direction)
} else {
position = (SEGLEN << 7); // at speed=0, make it static in the center (this enables to use modulators only)
}
for (int i = 0; i < SEGLEN; i++) {
uint32_t dist = abs(position - (i << 8));
if (dist < (radius << 8)) {
uint32_t color = SEGMENT.color_from_palette(i * 255 / SEGLEN, false, false, 0);
uint8_t blend = dist / radius; // linear gradient note: dist is in subpixels, radius in pixels, result is [0, 255] since dist < radius*256
if (SEGMENT.custom2) {
uint8_t modVal; // modulation value
if (SEGMENT.check1) {
modVal = (sin16_t((i * SEGMENT.custom2 << 6) + (strip.now * SEGMENT.custom3 << 5)) >> 8) + 128; // sine modulation: regular "Zebra" stripes
} else {
modVal = perlin16((i * SEGMENT.custom2 << 7), strip.now * SEGMENT.custom3 << 5) >> 8; // perlin noise modulation
}
color = color_fade(color, modVal, true); // dim by modulator value
}
SEGMENT.setPixelColor(i, color_blend(color, SEGCOLOR(1), blend)); // blend to background color
} else {
SEGMENT.setPixelColor(i, SEGCOLOR(1));
}
}
return FRAMETIME;
}
static const char _data_FX_MODE_SHIMMER[] PROGMEM = "Shimmer@Speed,Interval,Size,Granular,Flow,Zebra,Reverse,Sporadic;Fx,Bg,Cx;!;1;pal=15,sx=220,ix=10,c2=0,c3=0";
#ifndef WLED_DISABLE_2D
///////////////////////////////////////////////////////////////////////////////
@@ -5196,162 +5121,112 @@ static const char _data_FX_MODE_2DFRIZZLES[] PROGMEM = "Frizzles@X frequency,Y f
///////////////////////////////////////////
// 2D Cellular Automata Game of life //
///////////////////////////////////////////
typedef struct Cell {
uint8_t alive : 1, faded : 1, toggleStatus : 1, edgeCell: 1, oscillatorCheck : 1, spaceshipCheck : 1, unused : 2;
} Cell;
typedef struct ColorCount {
CRGB color;
int8_t count;
} colorCount;
uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https://natureofcode.com/book/chapter-7-cellular-automata/
// and https://github.com/DougHaber/nlife-color , Modified By: Brandon Butler
uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https://natureofcode.com/book/chapter-7-cellular-automata/ and https://github.com/DougHaber/nlife-color
if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up
const int cols = SEG_W, rows = SEG_H;
const unsigned maxIndex = cols * rows;
if (!SEGENV.allocateData(SEGMENT.length() * sizeof(Cell))) return mode_static(); // allocation failed
const int cols = SEG_W;
const int rows = SEG_H;
const auto XY = [&](int x, int y) { return (x%cols) + (y%rows) * cols; };
const unsigned dataSize = sizeof(CRGB) * SEGMENT.length(); // using width*height prevents reallocation if mirroring is enabled
const int crcBufferLen = 2; //(SEGMENT.width() + SEGMENT.height())*71/100; // roughly sqrt(2)/2 for better repetition detection (Ewowi)
Cell *cells = reinterpret_cast<Cell*> (SEGENV.data);
if (!SEGENV.allocateData(dataSize + sizeof(uint16_t)*crcBufferLen)) return mode_static(); //allocation failed
CRGB *prevLeds = reinterpret_cast<CRGB*>(SEGENV.data);
uint16_t *crcBuffer = reinterpret_cast<uint16_t*>(SEGENV.data + dataSize);
uint16_t& generation = SEGENV.aux0, &gliderLength = SEGENV.aux1; // rename aux variables for clarity
bool mutate = SEGMENT.check3;
uint8_t blur = map(SEGMENT.custom1, 0, 255, 255, 4);
CRGB backgroundColor = SEGCOLOR(1);
uint32_t bgColor = SEGCOLOR(1);
uint32_t birthColor = SEGMENT.color_from_palette(128, false, PALETTE_SOLID_WRAP, 255);
if (SEGENV.call == 0 || strip.now - SEGMENT.step > 3000) {
SEGENV.step = strip.now;
SEGENV.aux0 = 0;
bool setup = SEGENV.call == 0;
if (setup) {
// Calculate glider length LCM(rows,cols)*4 once
unsigned a = rows, b = cols;
while (b) { unsigned t = b; b = a % b; a = t; }
gliderLength = (cols * rows / a) << 2;
}
if (abs(long(strip.now) - long(SEGENV.step)) > 2000) SEGENV.step = 0; // Timebase jump fix
bool paused = SEGENV.step > strip.now;
// Setup New Game of Life
if ((!paused && generation == 0) || setup) {
SEGENV.step = strip.now + 1280; // show initial state for 1.28 seconds
generation = 1;
paused = true;
//Setup Grid
memset(cells, 0, maxIndex * sizeof(Cell));
for (unsigned i = 0; i < maxIndex; i++) {
bool isAlive = !hw_random8(3); // ~33%
cells[i].alive = isAlive;
cells[i].faded = !isAlive;
unsigned x = i % cols, y = i / cols;
cells[i].edgeCell = (x == 0 || x == cols-1 || y == 0 || y == rows-1);
SEGMENT.setPixelColor(i, isAlive ? SEGMENT.color_from_palette(hw_random8(), false, PALETTE_SOLID_WRAP, 0) : bgColor);
//give the leds random state and colors (based on intensity, colors from palette or all posible colors are chosen)
for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) {
unsigned state = hw_random8()%2;
if (state == 0)
SEGMENT.setPixelColorXY(x,y, backgroundColor);
else
SEGMENT.setPixelColorXY(x,y, SEGMENT.color_from_palette(hw_random8(), false, PALETTE_SOLID_WRAP, 255));
}
}
if (paused || (strip.now - SEGENV.step < 1000 / map(SEGMENT.speed,0,255,1,42))) {
// Redraw if paused or between updates to remove blur
for (unsigned i = maxIndex; i--; ) {
if (!cells[i].alive) {
uint32_t cellColor = SEGMENT.getPixelColor(i);
if (cellColor != bgColor) {
uint32_t newColor;
bool needsColor = false;
if (cells[i].faded) { newColor = bgColor; needsColor = true; }
else {
uint32_t blended = color_blend(cellColor, bgColor, 2);
if (blended == cellColor) { blended = bgColor; cells[i].faded = 1; }
newColor = blended; needsColor = true;
}
if (needsColor) SEGMENT.setPixelColor(i, newColor);
}
}
}
for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) prevLeds[XY(x,y)] = CRGB::Black;
memset(crcBuffer, 0, sizeof(uint16_t)*crcBufferLen);
} else if (strip.now - SEGENV.step < FRAMETIME_FIXED * (uint32_t)map(SEGMENT.speed,0,255,64,4)) {
// update only when appropriate time passes (in 42 FPS slots)
return FRAMETIME;
}
// Repeat detection
bool updateOscillator = generation % 16 == 0;
bool updateSpaceship = gliderLength && generation % gliderLength == 0;
bool repeatingOscillator = true, repeatingSpaceship = true, emptyGrid = true;
//copy previous leds (save previous generation)
//NOTE: using lossy getPixelColor() is a benefit as endlessly repeating patterns will eventually fade out causing a reset
for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) prevLeds[XY(x,y)] = SEGMENT.getPixelColorXY(x,y);
unsigned cIndex = maxIndex-1;
for (unsigned y = rows; y--; ) for (unsigned x = cols; x--; cIndex--) {
Cell& cell = cells[cIndex];
//calculate new leds
for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) {
if (cell.alive) emptyGrid = false;
if (cell.oscillatorCheck != cell.alive) repeatingOscillator = false;
if (cell.spaceshipCheck != cell.alive) repeatingSpaceship = false;
if (updateOscillator) cell.oscillatorCheck = cell.alive;
if (updateSpaceship) cell.spaceshipCheck = cell.alive;
colorCount colorsCount[9]; // count the different colors in the 3*3 matrix
for (int i=0; i<9; i++) colorsCount[i] = {backgroundColor, 0}; // init colorsCount
unsigned neighbors = 0, aliveParents = 0, parentIdx[3];
// Count alive neighbors
for (int i = -1; i <= 1; i++) for (int j = -1; j <= 1; j++) if (i || j) {
int nX = x + j, nY = y + i;
if (cell.edgeCell) {
nX = (nX + cols) % cols;
nY = (nY + rows) % rows;
}
unsigned nIndex = nX + nY * cols;
Cell& neighbor = cells[nIndex];
if (neighbor.alive) {
// iterate through neighbors and count them and their different colors
int neighbors = 0;
for (int i = -1; i <= 1; i++) for (int j = -1; j <= 1; j++) { // iterate through 3*3 matrix
if (i==0 && j==0) continue; // ignore itself
// wrap around segment
int xx = x+i, yy = y+j;
if (x+i < 0) xx = cols-1; else if (x+i >= cols) xx = 0;
if (y+j < 0) yy = rows-1; else if (y+j >= rows) yy = 0;
unsigned xy = XY(xx, yy); // previous cell xy to check
// count different neighbours and colors
if (prevLeds[xy] != backgroundColor) {
neighbors++;
if (!neighbor.toggleStatus && neighbors < 4) { // Alive and not dying
parentIdx[aliveParents++] = nIndex;
}
bool colorFound = false;
int k;
for (k=0; k<9 && colorsCount[k].count != 0; k++)
if (colorsCount[k].color == prevLeds[xy]) {
colorsCount[k].count++;
colorFound = true;
}
if (!colorFound) colorsCount[k] = {prevLeds[xy], 1}; //add new color found in the array
}
} // i,j
// Rules of Life
uint32_t col = uint32_t(prevLeds[XY(x,y)]) & 0x00FFFFFF; // uint32_t operator returns RGBA, we want RGBW -> cut off "alpha" byte
uint32_t bgc = RGBW32(backgroundColor.r, backgroundColor.g, backgroundColor.b, 0);
if ((col != bgc) && (neighbors < 2)) SEGMENT.setPixelColorXY(x,y, bgc); // Loneliness
else if ((col != bgc) && (neighbors > 3)) SEGMENT.setPixelColorXY(x,y, bgc); // Overpopulation
else if ((col == bgc) && (neighbors == 3)) { // Reproduction
// find dominant color and assign it to a cell
colorCount dominantColorCount = {backgroundColor, 0};
for (int i=0; i<9 && colorsCount[i].count != 0; i++)
if (colorsCount[i].count > dominantColorCount.count) dominantColorCount = colorsCount[i];
// assign the dominant color w/ a bit of randomness to avoid "gliders"
if (dominantColorCount.count > 0 && hw_random8(128)) SEGMENT.setPixelColorXY(x,y, dominantColorCount.color);
} else if ((col == bgc) && (neighbors == 2) && !hw_random8(128)) { // Mutation
SEGMENT.setPixelColorXY(x,y, SEGMENT.color_from_palette(hw_random8(), false, PALETTE_SOLID_WRAP, 255));
}
// else do nothing!
} //x,y
uint32_t newColor;
bool needsColor = false;
// calculate CRC16 of leds
uint16_t crc = crc16((const unsigned char*)prevLeds, dataSize);
// check if we had same CRC and reset if needed
bool repetition = false;
for (int i=0; i<crcBufferLen && !repetition; i++) repetition = (crc == crcBuffer[i]); // (Ewowi)
// same CRC would mean image did not change or was repeating itself
if (!repetition) SEGENV.step = strip.now; //if no repetition avoid reset
// remember CRCs across frames
crcBuffer[SEGENV.aux0] = crc;
++SEGENV.aux0 %= crcBufferLen;
if (cell.alive && (neighbors < 2 || neighbors > 3)) { // Loneliness or Overpopulation
cell.toggleStatus = 1;
if (blur == 255) cell.faded = 1;
newColor = cell.faded ? bgColor : color_blend(SEGMENT.getPixelColor(cIndex), bgColor, blur);
needsColor = true;
}
else if (!cell.alive) {
byte mutationRoll = mutate ? hw_random8(128) : 1; // if 0: 3 neighbor births fail and 2 neighbor births mutate
if ((neighbors == 3 && mutationRoll) || (mutate && neighbors == 2 && !mutationRoll)) { // Reproduction or Mutation
cell.toggleStatus = 1;
cell.faded = 0;
if (aliveParents) {
// Set color based on random neighbor
unsigned parentIndex = parentIdx[random8(aliveParents)];
birthColor = SEGMENT.getPixelColor(parentIndex);
}
newColor = birthColor;
needsColor = true;
}
else if (!cell.faded) {// No change, fade dead cells
uint32_t cellColor = SEGMENT.getPixelColor(cIndex);
uint32_t blended = color_blend(cellColor, bgColor, blur);
if (blended == cellColor) { blended = bgColor; cell.faded = 1; }
newColor = blended;
needsColor = true;
}
}
if (needsColor) SEGMENT.setPixelColor(cIndex, newColor);
}
// Loop through cells, if toggle, swap alive status
for (unsigned i = maxIndex; i--; ) {
cells[i].alive ^= cells[i].toggleStatus;
cells[i].toggleStatus = 0;
}
if (repeatingOscillator || repeatingSpaceship || emptyGrid) {
generation = 0; // reset on next call
SEGENV.step += 1024; // pause final generation for ~1 second
}
else {
++generation;
SEGENV.step = strip.now;
}
return FRAMETIME;
} // mode_2Dgameoflife()
static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!,,Blur,,,,,Mutation;!,!;!;2;pal=11,sx=128";
static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!;!,!;!;2";
/////////////////////////
@@ -7398,7 +7273,6 @@ uint16_t mode_2DGEQ(void) { // By Will Tatam. Code reduction by Ewoud Wijma.
if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up
const int NUM_BANDS = map(SEGMENT.custom1, 0, 255, 1, 16);
const int CENTER_BIN = map(SEGMENT.custom3, 0, 31, 0, 15);
const int cols = SEG_W;
const int rows = SEG_H;
@@ -7420,14 +7294,8 @@ uint16_t mode_2DGEQ(void) { // By Will Tatam. Code reduction by Ewoud Wijma.
if ((fadeoutDelay <= 1 ) || ((SEGENV.call % fadeoutDelay) == 0)) SEGMENT.fadeToBlackBy(SEGMENT.speed);
for (int x=0; x < cols; x++) {
int band = map(x, 0, cols, 0, NUM_BANDS);
if (NUM_BANDS < 16) {
int startBin = constrain(CENTER_BIN - NUM_BANDS/2, 0, 15 - NUM_BANDS + 1);
if(NUM_BANDS <= 1)
band = CENTER_BIN; // map() does not work for single band
else
band = map(band, 0, NUM_BANDS - 1, startBin, startBin + NUM_BANDS - 1);
}
uint8_t band = map(x, 0, cols, 0, NUM_BANDS);
if (NUM_BANDS < 16) band = map(band, 0, NUM_BANDS - 1, 0, 15); // always use full range. comment out this line to get the previous behaviour.
band = constrain(band, 0, 15);
unsigned colorIndex = band * 17;
int barHeight = map(fftResult[band], 0, 255, 0, rows); // do not subtract -1 from rows here
@@ -7449,7 +7317,7 @@ uint16_t mode_2DGEQ(void) { // By Will Tatam. Code reduction by Ewoud Wijma.
return FRAMETIME;
} // mode_2DGEQ()
static const char _data_FX_MODE_2DGEQ[] PROGMEM = "GEQ@Fade speed,Ripple decay,# of bands,,Bin,Color bars;!,,Peaks;!;2f;c1=255,c2=64,pal=11,si=0,c3=0";
static const char _data_FX_MODE_2DGEQ[] PROGMEM = "GEQ@Fade speed,Ripple decay,# of bands,,,Color bars;!,,Peaks;!;2f;c1=255,c2=64,pal=11,si=0"; // Beatsin
/////////////////////////
@@ -10937,7 +10805,6 @@ void WS2812FX::setupEffectData() {
addEffect(FX_MODE_FLOWSTRIPE, &mode_FlowStripe, _data_FX_MODE_FLOWSTRIPE);
addEffect(FX_MODE_WAVESINS, &mode_wavesins, _data_FX_MODE_WAVESINS);
addEffect(FX_MODE_ROCKTAVES, &mode_rocktaves, _data_FX_MODE_ROCKTAVES);
addEffect(FX_MODE_SHIMMER, &mode_shimmer, _data_FX_MODE_SHIMMER);
// --- 2D effects ---
#ifndef WLED_DISABLE_2D

View File

@@ -88,26 +88,23 @@ extern byte realtimeMode; // used in getMappedPixelIndex()
#endif
#define FPS_CALC_SHIFT 7 // bit shift for fixed point math
// heap memory limit for effects data, pixel buffers try to reserve it if PSRAM is available
/* each segment uses 82 bytes of SRAM memory, so if you're application fails because of
insufficient memory, decreasing MAX_NUM_SEGMENTS may help */
#ifdef ESP8266
#define MAX_NUM_SEGMENTS 16
/* How much data bytes all segments combined may allocate */
#define MAX_SEGMENT_DATA (6*1024) // 6k by default
#define MAX_SEGMENT_DATA 5120
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
#define MAX_NUM_SEGMENTS 32
#define MAX_SEGMENT_DATA (20*1024) // 20k by default (S2 is short on free RAM), limit does not apply if PSRAM is available
#define MAX_NUM_SEGMENTS 20
#define MAX_SEGMENT_DATA (MAX_NUM_SEGMENTS*512) // 10k by default (S2 is short on free RAM)
#else
#ifdef BOARD_HAS_PSRAM
#define MAX_NUM_SEGMENTS 64
#else
#define MAX_NUM_SEGMENTS 32
#endif
#define MAX_SEGMENT_DATA (64*1024) // 64k by default, limit does not apply if PSRAM is available
#define MAX_NUM_SEGMENTS 32 // warning: going beyond 32 may consume too much RAM for stable operation
#define MAX_SEGMENT_DATA (MAX_NUM_SEGMENTS*1280) // 40k by default
#endif
/* How much data bytes each segment should max allocate to leave enough space for other segments,
assuming each segment uses the same amount of data. 256 for ESP8266, 640 for ESP32. */
#define FAIR_DATA_PER_SEG (MAX_SEGMENT_DATA / MAX_NUM_SEGMENTS)
#define FAIR_DATA_PER_SEG (MAX_SEGMENT_DATA / WS2812FX::getMaxSegments())
#define MIN_SHOW_DELAY (_frametime < 16 ? 8 : 15)
@@ -320,7 +317,6 @@ extern byte realtimeMode; // used in getMappedPixelIndex()
#define FX_MODE_DJLIGHT 159
#define FX_MODE_2DFUNKYPLANK 160
//#define FX_MODE_2DCENTERBARS 161
#define FX_MODE_SHIMMER 161 // gap fill, non SR 1D effect
#define FX_MODE_2DPULSER 162
#define FX_MODE_BLURZ 163
#define FX_MODE_2DDRIFT 164
@@ -537,6 +533,7 @@ class Segment {
protected:
inline static unsigned getUsedSegmentData() { return Segment::_usedSegmentData; }
inline static void addUsedSegmentData(int len) { Segment::_usedSegmentData += len; }
inline uint32_t *getPixels() const { return pixels; }
@@ -603,8 +600,8 @@ class Segment {
, _t(nullptr)
{
DEBUGFX_PRINTF_P(PSTR("-- Creating segment: %p [%d,%d:%d,%d]\n"), this, (int)start, (int)stop, (int)startY, (int)stopY);
// allocate render buffer (always entire segment), prefer PSRAM if DRAM is running low. Note: impact on FPS with PSRAM buffer is low (<2% with QSPI PSRAM)
pixels = static_cast<uint32_t*>(allocate_buffer(length() * sizeof(uint32_t), BFRALLOC_PREFER_PSRAM | BFRALLOC_NOBYTEACCESS | BFRALLOC_CLEAR));
// allocate render buffer (always entire segment)
pixels = static_cast<uint32_t*>(d_calloc(sizeof(uint32_t), length())); // error handling is also done in isActive()
if (!pixels) {
DEBUGFX_PRINTLN(F("!!! Not enough RAM for pixel buffer !!!"));
extern byte errorFlag;
@@ -625,11 +622,8 @@ class Segment {
DEBUGFX_PRINTLN();
#endif
clearName();
#ifdef WLED_ENABLE_GIF
endImagePlayback(this);
#endif
deallocateData();
p_free(pixels);
d_free(pixels);
}
Segment& operator= (const Segment &orig); // copy assignment
@@ -652,7 +646,7 @@ class Segment {
inline uint16_t groupLength() const { return grouping + spacing; }
inline uint8_t getLightCapabilities() const { return _capabilities; }
inline void deactivate() { setGeometry(0,0); }
inline Segment &clearName() { p_free(name); name = nullptr; return *this; }
inline Segment &clearName() { d_free(name); name = nullptr; return *this; }
inline Segment &setName(const String &name) { return setName(name.c_str()); }
inline static unsigned vLength() { return Segment::_vLength; }
@@ -678,7 +672,6 @@ class Segment {
inline uint16_t dataSize() const { return _dataLen; }
bool allocateData(size_t len); // allocates effect data buffer in heap and clears it
void deallocateData(); // deallocates (frees) effect data buffer from heap
inline static unsigned getUsedSegmentData() { return Segment::_usedSegmentData; }
/**
* Flags that before the next effect is calculated,
* the internal segment state should be reset.
@@ -875,8 +868,8 @@ class WS2812FX {
}
~WS2812FX() {
p_free(_pixels);
p_free(_pixelCCT); // just in case
d_free(_pixels);
d_free(_pixelCCT); // just in case
d_free(customMappingTable);
_mode.clear();
_modeData.clear();

View File

@@ -8,6 +8,7 @@
Parts of the code adapted from WLED Sound Reactive
*/
#include "wled.h"
#include "palettes.h"
// setUpMatrix() - constructs ledmap array from matrix of panels with WxH pixels
// this converts physical (possibly irregular) LED arrangement into well defined
@@ -145,7 +146,7 @@ void WS2812FX::setUpMatrix() {
#ifndef WLED_DISABLE_2D
// pixel is clipped if it falls outside clipping range
// if clipping start > stop the clipping range is inverted
bool Segment::isPixelXYClipped(int x, int y) const {
bool IRAM_ATTR_YN Segment::isPixelXYClipped(int x, int y) const {
if (blendingStyle != BLEND_STYLE_FADE && isInTransition() && _clipStart != _clipStop) {
const bool invertX = _clipStart > _clipStop;
const bool invertY = _clipStartY > _clipStopY;
@@ -185,7 +186,7 @@ bool Segment::isPixelXYClipped(int x, int y) const {
void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) const
{
if (!isActive()) return; // not active
if ((unsigned)x >= vWidth() || (unsigned)y >= vHeight()) return; // if pixel would fall out of virtual segment just exit
if (x >= (int)vWidth() || y >= (int)vHeight() || x < 0 || y < 0) return; // if pixel would fall out of virtual segment just exit
setPixelColorXYRaw(x, y, col);
}
@@ -235,7 +236,7 @@ void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) const
// returns RGBW values of pixel
uint32_t IRAM_ATTR_YN Segment::getPixelColorXY(int x, int y) const {
if (!isActive()) return 0; // not active
if ((unsigned)x >= vWidth() || (unsigned)y >= vHeight()) return 0; // if pixel would fall out of virtual segment just exit
if (x >= (int)vWidth() || y >= (int)vHeight() || x<0 || y<0) return 0; // if pixel would fall out of virtual segment just exit
return getPixelColorXYRaw(x,y);
}
@@ -245,42 +246,52 @@ void Segment::blur2D(uint8_t blur_x, uint8_t blur_y, bool smear) const {
const unsigned cols = vWidth();
const unsigned rows = vHeight();
const auto XY = [&](unsigned x, unsigned y){ return x + y*cols; };
uint32_t lastnew; // not necessary to initialize lastnew and last, as both will be initialized by the first loop iteration
uint32_t last;
if (blur_x) {
const uint8_t keepx = smear ? 255 : 255 - blur_x;
const uint8_t seepx = blur_x >> 1;
for (unsigned row = 0; row < rows; row++) { // blur rows (x direction)
// handle first pixel in row to avoid conditional in loop (faster)
uint32_t cur = getPixelColorRaw(XY(0, row));
uint32_t carryover = fast_color_scale(cur, seepx);
setPixelColorRaw(XY(0, row), fast_color_scale(cur, keepx));
for (unsigned x = 1; x < cols; x++) {
cur = getPixelColorRaw(XY(x, row));
uint32_t part = fast_color_scale(cur, seepx);
cur = fast_color_scale(cur, keepx);
cur = color_add(cur, carryover);
setPixelColorRaw(XY(x - 1, row), color_add(getPixelColorRaw(XY(x-1, row)), part)); // previous pixel
setPixelColorRaw(XY(x, row), cur); // current pixel
uint32_t carryover = BLACK;
uint32_t curnew = BLACK;
for (unsigned x = 0; x < cols; x++) {
uint32_t cur = getPixelColorRaw(XY(x, row));
uint32_t part = color_fade(cur, seepx);
curnew = color_fade(cur, keepx);
if (x > 0) {
if (carryover) curnew = color_add(curnew, carryover);
uint32_t prev = color_add(lastnew, part);
// optimization: only set pixel if color has changed
if (last != prev) setPixelColorRaw(XY(x - 1, row), prev);
} else setPixelColorRaw(XY(x, row), curnew); // first pixel
lastnew = curnew;
last = cur; // save original value for comparison on next iteration
carryover = part;
}
setPixelColorRaw(XY(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++) {
// handle first pixel in column
uint32_t cur = getPixelColorRaw(XY(col, 0));
uint32_t carryover = fast_color_scale(cur, seepy);
setPixelColorRaw(XY(col, 0), fast_color_scale(cur, keepy));
for (unsigned y = 1; y < rows; y++) {
cur = getPixelColorRaw(XY(col, y));
uint32_t part = fast_color_scale(cur, seepy);
cur = fast_color_scale(cur, keepy);
cur = color_add(cur, carryover);
setPixelColorRaw(XY(col, y - 1), color_add(getPixelColorRaw(XY(col, y-1)), part)); // previous pixel
setPixelColorRaw(XY(col, y), cur); // current pixel
uint32_t carryover = BLACK;
uint32_t curnew = BLACK;
for (unsigned y = 0; y < rows; y++) {
uint32_t cur = getPixelColorRaw(XY(col, y));
uint32_t part = color_fade(cur, seepy);
curnew = color_fade(cur, keepy);
if (y > 0) {
if (carryover) curnew = color_add(curnew, carryover);
uint32_t prev = color_add(lastnew, part);
// optimization: only set pixel if color has changed
if (last != prev) setPixelColorRaw(XY(col, y - 1), prev);
} else setPixelColorRaw(XY(col, y), curnew); // first pixel
lastnew = curnew;
last = cur; //save original value for comparison on next iteration
carryover = part;
}
setPixelColorRaw(XY(col, rows - 1), curnew);
}
}
}

View File

@@ -11,6 +11,11 @@
*/
#include "wled.h"
#include "FXparticleSystem.h" // TODO: better define the required function (mem service) in FX.h?
#include "palettes.h"
#ifndef DEFAULT_LED_COLOR_ORDER
#define DEFAULT_LED_COLOR_ORDER COL_ORDER_GRB //default to GRB
#endif
/*
Custom per-LED mapping has moved!
@@ -67,10 +72,10 @@ Segment::Segment(const Segment &orig) {
if (!stop) return; // nothing to do if segment is inactive/invalid
if (orig.pixels) {
// allocate pixel buffer: prefer IRAM/PSRAM
pixels = static_cast<uint32_t*>(allocate_buffer(orig.length() * sizeof(uint32_t), BFRALLOC_PREFER_PSRAM | BFRALLOC_NOBYTEACCESS));
pixels = static_cast<uint32_t*>(d_malloc(sizeof(uint32_t) * orig.length()));
if (pixels) {
memcpy(pixels, orig.pixels, sizeof(uint32_t) * orig.length());
if (orig.name) { name = static_cast<char*>(allocate_buffer(strlen(orig.name)+1, BFRALLOC_PREFER_PSRAM)); if (name) strcpy(name, orig.name); }
if (orig.name) { name = static_cast<char*>(d_malloc(strlen(orig.name)+1)); if (name) strcpy(name, orig.name); }
if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); }
} else {
DEBUGFX_PRINTLN(F("!!! Not enough RAM for pixel buffer !!!"));
@@ -96,10 +101,10 @@ Segment& Segment::operator= (const Segment &orig) {
//DEBUG_PRINTF_P(PSTR("-- Copying segment: %p -> %p\n"), &orig, this);
if (this != &orig) {
// clean destination
if (name) { p_free(name); name = nullptr; }
if (name) { d_free(name); name = nullptr; }
if (_t) stopTransition(); // also erases _t
deallocateData();
p_free(pixels);
d_free(pixels);
// copy source
memcpy((void*)this, (void*)&orig, sizeof(Segment));
// erase pointers to allocated data
@@ -110,10 +115,10 @@ Segment& Segment::operator= (const Segment &orig) {
// copy source data
if (orig.pixels) {
// allocate pixel buffer: prefer IRAM/PSRAM
pixels = static_cast<uint32_t*>(allocate_buffer(orig.length() * sizeof(uint32_t), BFRALLOC_PREFER_PSRAM | BFRALLOC_NOBYTEACCESS));
pixels = static_cast<uint32_t*>(d_malloc(sizeof(uint32_t) * orig.length()));
if (pixels) {
memcpy(pixels, orig.pixels, sizeof(uint32_t) * orig.length());
if (orig.name) { name = static_cast<char*>(allocate_buffer(strlen(orig.name)+1, BFRALLOC_PREFER_PSRAM)); if (name) strcpy(name, orig.name); }
if (orig.name) { name = static_cast<char*>(d_malloc(strlen(orig.name)+1)); if (name) strcpy(name, orig.name); }
if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); }
} else {
DEBUG_PRINTLN(F("!!! Not enough RAM for pixel buffer !!!"));
@@ -129,10 +134,10 @@ Segment& Segment::operator= (const Segment &orig) {
Segment& Segment::operator= (Segment &&orig) noexcept {
//DEBUG_PRINTF_P(PSTR("-- Moving segment: %p -> %p\n"), &orig, this);
if (this != &orig) {
if (name) { p_free(name); name = nullptr; } // free old name
if (name) { d_free(name); name = nullptr; } // free old name
if (_t) stopTransition(); // also erases _t
deallocateData(); // free old runtime data
p_free(pixels); // free old pixel buffer
d_free(pixels); // free old pixel buffer
// move source data
memcpy((void*)this, (void*)&orig, sizeof(Segment));
orig.name = nullptr;
@@ -146,38 +151,35 @@ Segment& Segment::operator= (Segment &&orig) noexcept {
// allocates effect data buffer on heap and initialises (erases) it
bool Segment::allocateData(size_t len) {
if (len == 0) return false; // nothing to do
if (data && _dataLen >= len) { // already allocated enough (reduce fragmentation)
if (len == 0) return false; // nothing to do
if (data && _dataLen >= len) { // already allocated enough (reduce fragmentation)
if (call == 0) {
if (_dataLen < FAIR_DATA_PER_SEG) { // segment data is small
//DEBUG_PRINTF_P(PSTR("-- Clearing data (%d): %p\n"), len, this);
memset(data, 0, len); // erase buffer if called during effect initialisation
return true; // no need to reallocate
}
//DEBUG_PRINTF_P(PSTR("-- Clearing data (%d): %p\n"), len, this);
memset(data, 0, len); // erase buffer if called during effect initialisation
}
else
return true;
return true;
}
//DEBUG_PRINTF_P(PSTR("-- Allocating data (%d): %p\n"), len, this);
// limit to MAX_SEGMENT_DATA if there is no PSRAM, otherwise prefer functionality over speed
#ifndef BOARD_HAS_PSRAM
if (Segment::getUsedSegmentData() + len - _dataLen > MAX_SEGMENT_DATA) {
// not enough memory
DEBUG_PRINTF_P(PSTR("SegmentData limit reached: %d/%d\n"), len, Segment::getUsedSegmentData());
DEBUG_PRINTF_P(PSTR("!!! Not enough RAM: %d/%d !!!\n"), len, Segment::getUsedSegmentData());
errorFlag = ERR_NORAM;
return false;
}
#endif
// prefer DRAM over SPI RAM on ESP32 since it is slow
if (data) {
d_free(data); // free data and try to allocate again (segment buffer may be blocking contiguous heap)
Segment::addUsedSegmentData(-_dataLen); // subtract buffer size
data = (byte*)d_realloc_malloc(data, len); // realloc with malloc fallback
if (!data) {
data = nullptr;
Segment::addUsedSegmentData(-_dataLen); // subtract original buffer size
_dataLen = 0; // reset data length
}
}
data = static_cast<byte*>(allocate_buffer(len, BFRALLOC_PREFER_DRAM | BFRALLOC_CLEAR)); // prefer DRAM over PSRAM for speed
else data = (byte*)d_malloc(len);
if (data) {
Segment::addUsedSegmentData(len);
memset(data, 0, len); // erase buffer
Segment::addUsedSegmentData(len - _dataLen);
_dataLen = len;
//DEBUG_PRINTF_P(PSTR("--- Allocated data (%p): %d/%d -> %p\n"), this, len, Segment::getUsedSegmentData(), data);
return true;
@@ -211,11 +213,7 @@ void Segment::deallocateData() {
void Segment::resetIfRequired() {
if (!reset || !isActive()) return;
//DEBUG_PRINTF_P(PSTR("-- Segment reset: %p\n"), this);
if (data && _dataLen > 0) {
if (_dataLen > FAIR_DATA_PER_SEG) deallocateData(); // do not keep large allocations
else memset(data, 0, _dataLen); // can prevent heap fragmentation
DEBUG_PRINTF_P(PSTR("-- Segment %p reset, data cleared\n"), this);
}
if (data && _dataLen > 0) memset(data, 0, _dataLen); // prevent heap fragmentation (just erase buffer instead of deallocateData())
if (pixels) for (size_t i = 0; i < length(); i++) pixels[i] = BLACK; // clear pixel buffer
next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0;
reset = false;
@@ -225,12 +223,8 @@ void Segment::resetIfRequired() {
}
CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) {
// there is one randomy generated palette (1) followed by 4 palettes created from segment colors (2-5)
// those are followed by 7 fastled palettes (6-12) and 59 gradient palettes (13-71)
// then come the custom palettes (255,254,...) growing downwards from 255 (255 being 1st custom palette)
// palette 0 is a varying palette depending on effect and may be replaced by segment's color if so
// instructed in color_from_palette()
if (pal > FIXED_PALETTE_COUNT && pal <= 255-customPalettes.size()) pal = 0; // out of bounds palette
if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0;
if (pal > 245 && (customPalettes.size() == 0 || 255U-pal > customPalettes.size()-1)) pal = 0;
//default palette. Differs depending on effect
if (pal == 0) pal = _default_palette; // _default_palette is set in setMode()
switch (pal) {
@@ -266,13 +260,13 @@ CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) {
}
break;}
default: //progmem palettes
if (pal > 255 - customPalettes.size()) {
if (pal>245) {
targetPalette = customPalettes[255-pal]; // we checked bounds above
} else if (pal < DYNAMIC_PALETTE_COUNT+FASTLED_PALETTE_COUNT+1) { // palette 6 - 12, fastled palettes
targetPalette = *fastledPalettes[pal-DYNAMIC_PALETTE_COUNT-1];
} else if (pal < 13) { // palette 6 - 12, fastled palettes
targetPalette = *fastledPalettes[pal-6];
} else {
byte tcp[72];
memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[pal-(DYNAMIC_PALETTE_COUNT+FASTLED_PALETTE_COUNT)-1])), 72);
memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[pal-13])), 72);
targetPalette.loadDynamicGradientPalette(tcp);
}
break;
@@ -448,9 +442,6 @@ void Segment::setGeometry(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, ui
// apply change immediately
if (i2 <= i1) { //disable segment
#ifdef WLED_ENABLE_GIF
endImagePlayback(this);
#endif
deallocateData();
p_free(pixels);
pixels = nullptr;
@@ -469,9 +460,6 @@ void Segment::setGeometry(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, ui
#endif
// safety check
if (start >= stop || startY >= stopY) {
#ifdef WLED_ENABLE_GIF
endImagePlayback(this);
#endif
deallocateData();
p_free(pixels);
pixels = nullptr;
@@ -482,12 +470,9 @@ void Segment::setGeometry(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, ui
if (length() != oldLength) {
// allocate render buffer (always entire segment), prefer IRAM/PSRAM. Note: impact on FPS with PSRAM buffer is low (<2% with QSPI PSRAM) on S2/S3
p_free(pixels);
pixels = static_cast<uint32_t*>(allocate_buffer(length() * sizeof(uint32_t), BFRALLOC_PREFER_PSRAM | BFRALLOC_NOBYTEACCESS));
pixels = static_cast<uint32_t*>(d_malloc(sizeof(uint32_t) * length()));
if (!pixels) {
DEBUGFX_PRINTLN(F("!!! Not enough RAM for pixel buffer !!!"));
#ifdef WLED_ENABLE_GIF
endImagePlayback(this);
#endif
deallocateData();
errorFlag = ERR_NORAM_PX;
stop = 0;
@@ -585,7 +570,8 @@ Segment &Segment::setMode(uint8_t fx, bool loadDefaults) {
}
Segment &Segment::setPalette(uint8_t pal) {
if (pal <= 255-customPalettes.size() && pal > FIXED_PALETTE_COUNT) pal = 0; // not built in palette or custom palette
if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0; // built in palettes
if (pal > 245 && (customPalettes.size() == 0 || 255U-pal > customPalettes.size()-1)) pal = 0; // custom palettes
if (pal != palette) {
//DEBUG_PRINTF_P(PSTR("- Starting palette transition: %d\n"), pal);
startTransition(strip.getTransition(), blendingStyle != BLEND_STYLE_FADE); // start transition prior to change (no need to copy segment)
@@ -599,8 +585,8 @@ Segment &Segment::setName(const char *newName) {
if (newName) {
const int newLen = min(strlen(newName), (size_t)WLED_MAX_SEGNAME_LEN);
if (newLen) {
if (name) p_free(name); // free old name
name = static_cast<char*>(allocate_buffer(newLen+1, BFRALLOC_PREFER_PSRAM));
if (name) d_free(name); // free old name
name = static_cast<char*>(d_malloc(newLen+1));
if (mode == FX_MODE_2DSCROLLTEXT) startTransition(strip.getTransition(), true); // if the name changes in scrolling text mode, we need to copy the segment for blending
if (name) strlcpy(name, newName, newLen+1);
return *this;
@@ -691,7 +677,7 @@ uint16_t Segment::maxMappingLength() const {
#endif
// pixel is clipped if it falls outside clipping range
// if clipping start > stop the clipping range is inverted
bool Segment::isPixelClipped(int i) const {
bool IRAM_ATTR_YN Segment::isPixelClipped(int i) const {
if (blendingStyle != BLEND_STYLE_FADE && isInTransition() && _clipStart != _clipStop) {
bool invert = _clipStart > _clipStop; // ineverted start & stop
int start = invert ? _clipStop : _clipStart;
@@ -709,7 +695,7 @@ bool Segment::isPixelClipped(int i) const {
return false;
}
void WLED_O2_ATTR Segment::setPixelColor(int i, uint32_t col) const
void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) const
{
if (!isActive() || i < 0) return; // not active or invalid index
#ifndef WLED_DISABLE_2D
@@ -922,7 +908,7 @@ void Segment::setPixelColor(float i, uint32_t col, bool aa) const
}
#endif
uint32_t WLED_O2_ATTR Segment::getPixelColor(int i) const
uint32_t IRAM_ATTR_YN Segment::getPixelColor(int i) const
{
if (!isActive() || i < 0) return 0; // not active or invalid index
@@ -1061,7 +1047,7 @@ void Segment::fadeToSecondaryBy(uint8_t fadeBy) const {
void Segment::fadeToBlackBy(uint8_t fadeBy) const {
if (!isActive() || fadeBy == 0) return; // optimization - no scaling to apply
const size_t rlength = rawLength(); // calculate only once
for (unsigned i = 0; i < rlength; i++) setPixelColorRaw(i, fast_color_scale(getPixelColorRaw(i), 255-fadeBy));
for (unsigned i = 0; i < rlength; i++) setPixelColorRaw(i, color_fade(getPixelColorRaw(i), 255-fadeBy));
}
/*
@@ -1081,19 +1067,25 @@ void Segment::blur(uint8_t blur_amount, bool smear) const {
uint8_t keep = smear ? 255 : 255 - blur_amount;
uint8_t seep = blur_amount >> 1;
unsigned vlength = vLength();
// handle first pixel to avoid conditional in loop (faster)
uint32_t cur = getPixelColorRaw(0);
uint32_t carryover = fast_color_scale(cur, seep);
setPixelColorRaw(0, fast_color_scale(cur, keep));
for (unsigned i = 1; i < vlength; i++) {
cur = getPixelColorRaw(i);
uint32_t part = fast_color_scale(cur, seep);
cur = fast_color_scale(cur, keep);
cur = color_add(cur, carryover);
setPixelColorRaw(i - 1, color_add(getPixelColorRaw(i - 1), part)); // previous pixel
setPixelColorRaw(i, cur); // current pixel
uint32_t carryover = BLACK;
uint32_t lastnew; // not necessary to initialize lastnew and last, as both will be initialized by the first loop iteration
uint32_t last;
uint32_t curnew = BLACK;
for (unsigned i = 0; i < vlength; i++) {
uint32_t cur = getPixelColorRaw(i);
uint32_t part = color_fade(cur, seep);
curnew = color_fade(cur, keep);
if (i > 0) {
if (carryover) curnew = color_add(curnew, carryover);
uint32_t prev = color_add(lastnew, part);
// optimization: only set pixel if color has changed
if (last != prev) setPixelColorRaw(i - 1, prev);
} else setPixelColorRaw(i, curnew); // first pixel
lastnew = curnew;
last = cur; // save original value for comparison on next iteration
carryover = part;
}
setPixelColorRaw(vlength - 1, curnew);
}
/*
@@ -1183,42 +1175,24 @@ void WS2812FX::finalizeInit() {
digitalCount = 0;
#endif
DEBUG_PRINTF_P(PSTR("Heap before buses: %d\n"), getFreeHeapSize());
// create buses/outputs
unsigned mem = 0;
unsigned maxI2S = 0;
for (const auto &bus : busConfigs) {
unsigned memB = bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount++ : 0); // does not include DMA/RMT buffer
mem += memB;
// estimate maximum I2S memory usage (only relevant for digital non-2pin busses)
#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(ESP8266)
#if defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S3)
const bool usesI2S = ((useParallelI2S && digitalCount <= 8) || (!useParallelI2S && digitalCount == 1));
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
const bool usesI2S = (useParallelI2S && digitalCount <= 8);
#else
const bool usesI2S = false;
#endif
if (Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) && usesI2S) {
#ifdef NPB_CONF_4STEP_CADENCE
constexpr unsigned stepFactor = 4; // 4 step cadence (4 bits per pixel bit)
#else
constexpr unsigned stepFactor = 3; // 3 step cadence (3 bits per pixel bit)
#endif
unsigned i2sCommonSize = stepFactor * bus.count * (3*Bus::hasRGB(bus.type)+Bus::hasWhite(bus.type)+Bus::hasCCT(bus.type)) * (Bus::is16bit(bus.type)+1);
if (i2sCommonSize > maxI2S) maxI2S = i2sCommonSize;
}
#endif
if (mem + maxI2S <= MAX_LED_MEMORY) {
BusManager::add(bus);
DEBUG_PRINTF_P(PSTR("Bus memory: %uB\n"), memB);
} else {
errorFlag = ERR_NORAM_PX; // alert UI
DEBUG_PRINTF_P(PSTR("Out of LED memory! Bus %d (%d) #%u not created."), (int)bus.type, (int)bus.count, digitalCount);
break;
for (auto bus : busConfigs) {
// Calculate what this bus would use with its current configuration
unsigned busMemUsage = bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount : 0);
// If memory exceeds limit, set count to minimum of current count and default length
if (mem + busMemUsage > MAX_LED_MEMORY) {
bus.count = min(bus.count, DEFAULT_LED_COUNT);
DEBUG_PRINTF_P(PSTR("Bus %d memory usage exceeds limit, setting count to %d\n"), (int)bus.type, bus.count);
}
if (BusManager::add(bus) != -1) {
mem += bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount : 0);
if (Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type)) digitalCount++;
} else break;
}
DEBUG_PRINTF_P(PSTR("LED buffer size: %uB/%uB\n"), mem + maxI2S, BusManager::memUsage());
busConfigs.clear();
busConfigs.shrink_to_fit();
@@ -1249,11 +1223,10 @@ void WS2812FX::finalizeInit() {
deserializeMap(); // (re)load default ledmap (will also setUpMatrix() if ledmap does not exist)
// allocate frame buffer after matrix has been set up (gaps!)
p_free(_pixels); // using realloc on large buffers can cause additional fragmentation instead of reducing it
// use PSRAM if available: there is no measurable perfomance impact between PSRAM and DRAM on S2/S3 with QSPI PSRAM for this buffer
_pixels = static_cast<uint32_t*>(allocate_buffer(getLengthTotal() * sizeof(uint32_t), BFRALLOC_ENFORCE_PSRAM | BFRALLOC_NOBYTEACCESS | BFRALLOC_CLEAR));
d_free(_pixels); // using realloc on large buffers can cause additional fragmentation instead of reducing it
_pixels = static_cast<uint32_t*>(d_malloc(getLengthTotal() * sizeof(uint32_t)));
DEBUG_PRINTF_P(PSTR("strip buffer size: %uB\n"), getLengthTotal() * sizeof(uint32_t));
DEBUG_PRINTF_P(PSTR("Heap after strip init: %uB\n"), getFreeHeapSize());
DEBUG_PRINTF_P(PSTR("Heap after strip init: %uB\n"), ESP.getFreeHeap());
}
void WS2812FX::service() {
@@ -1593,11 +1566,7 @@ void WS2812FX::blendSegment(const Segment &topSegment) const {
}
void WS2812FX::show() {
if (!_pixels) {
DEBUGFX_PRINTLN(F("Error: no _pixels!"));
errorFlag = ERR_NORAM;
return; // no pixels allocated, nothing to show
}
if (!_pixels) return; // no pixels allocated, nothing to show
unsigned long showNow = millis();
size_t diff = showNow - _lastShow;
@@ -1607,7 +1576,7 @@ void WS2812FX::show() {
// we need to keep track of each pixel's CCT when blending segments (if CCT is present)
// and then set appropriate CCT from that pixel during paint (see below).
if ((hasCCTBus() || correctWB) && !cctFromRgb)
_pixelCCT = static_cast<uint8_t*>(allocate_buffer(totalLen * sizeof(uint8_t), BFRALLOC_PREFER_PSRAM)); // allocate CCT buffer if necessary, prefer PSRAM
_pixelCCT = static_cast<uint8_t*>(d_malloc(totalLen * sizeof(uint8_t))); // allocate CCT buffer if necessary
if (_pixelCCT) memset(_pixelCCT, 127, totalLen); // set neutral (50:50) CCT
if (realtimeMode == REALTIME_MODE_INACTIVE || useMainSegmentOnly || realtimeOverride > REALTIME_OVERRIDE_NONE) {
@@ -1641,7 +1610,7 @@ void WS2812FX::show() {
}
Bus::setCCT(oldCCT); // restore old CCT for ABL adjustments
p_free(_pixelCCT);
d_free(_pixelCCT);
_pixelCCT = nullptr;
// some buses send asynchronously and this method will return before

View File

@@ -17,7 +17,8 @@
// local shared functions (used both in 1D and 2D system)
static int32_t calcForce_dv(const int8_t force, uint8_t &counter);
static bool checkBoundsAndWrap(int32_t &position, const int32_t max, const int32_t particleradius, const bool wrap); // returns false if out of bounds by more than particleradius
static uint32_t fast_color_scaleAdd(const uint32_t c1, const uint32_t c2, uint8_t scale = 255); // fast and accurate color adding with scaling (scales c2 before adding)
static uint32_t fast_color_add(CRGBW c1, const CRGBW c2, uint8_t scale = 255); // fast and accurate color adding with scaling (scales c2 before adding)
static uint32_t fast_color_scale(CRGBW c, const uint8_t scale); // fast scaling function using 32bit variable and pointer. note: keep 'scale' within 0-255
#endif
#ifndef WLED_DISABLE_PARTICLESYSTEM2D
@@ -624,7 +625,7 @@ void ParticleSystem2D::render() {
}
// calculate pixel positions and brightness distribution and render the particle to local buffer or global buffer
void WLED_O2_ATTR ParticleSystem2D::renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGBW& color, const bool wrapX, const bool wrapY) {
__attribute__((optimize("O2"))) void ParticleSystem2D::renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGBW& color, const bool wrapX, const bool wrapY) {
uint32_t size = particlesize;
if (advPartProps && advPartProps[particleindex].size > 0) // use advanced size properties (0 means use global size including single pixel rendering)
size = advPartProps[particleindex].size;
@@ -634,7 +635,7 @@ void WLED_O2_ATTR ParticleSystem2D::renderParticle(const uint32_t particleindex,
uint32_t y = particles[particleindex].y >> PS_P_RADIUS_SHIFT;
if (x <= (uint32_t)maxXpixel && y <= (uint32_t)maxYpixel) {
uint32_t index = x + (maxYpixel - y) * (maxXpixel + 1); // flip y coordinate (0,0 is bottom left in PS but top left in framebuffer)
framebuffer[index] = fast_color_scaleAdd(framebuffer[index], color, brightness);
framebuffer[index] = fast_color_add(framebuffer[index], color, brightness);
}
return;
}
@@ -686,10 +687,10 @@ void WLED_O2_ATTR ParticleSystem2D::renderParticle(const uint32_t particleindex,
memset(renderbuffer, 0, sizeof(renderbuffer)); // clear buffer
//particle size to pixels: < 64 is 4x4, < 128 is 6x6, < 192 is 8x8, bigger is 10x10
//first, render the pixel to the center of the renderbuffer, then apply 2D blurring
renderbuffer[4 + (4 * 10)] = fast_color_scaleAdd(renderbuffer[4 + (4 * 10)], color, pxlbrightness[0]); // order is: bottom left, bottom right, top right, top left
renderbuffer[5 + (4 * 10)] = fast_color_scaleAdd(renderbuffer[5 + (4 * 10)], color, pxlbrightness[1]);
renderbuffer[5 + (5 * 10)] = fast_color_scaleAdd(renderbuffer[5 + (5 * 10)], color, pxlbrightness[2]);
renderbuffer[4 + (5 * 10)] = fast_color_scaleAdd(renderbuffer[4 + (5 * 10)], color, pxlbrightness[3]);
renderbuffer[4 + (4 * 10)] = fast_color_add(renderbuffer[4 + (4 * 10)], color, pxlbrightness[0]); // order is: bottom left, bottom right, top right, top left
renderbuffer[5 + (4 * 10)] = fast_color_add(renderbuffer[5 + (4 * 10)], color, pxlbrightness[1]);
renderbuffer[5 + (5 * 10)] = fast_color_add(renderbuffer[5 + (5 * 10)], color, pxlbrightness[2]);
renderbuffer[4 + (5 * 10)] = fast_color_add(renderbuffer[4 + (5 * 10)], color, pxlbrightness[3]);
uint32_t rendersize = 2; // initialize render size, minimum is 4x4 pixels, it is incremented int he loop below to start with 4
uint32_t offset = 4; // offset to zero coordinate to write/read data in renderbuffer (actually needs to be 3, is decremented in the loop below)
uint32_t maxsize = advPartProps[particleindex].size;
@@ -747,7 +748,7 @@ void WLED_O2_ATTR ParticleSystem2D::renderParticle(const uint32_t particleindex,
continue;
}
uint32_t idx = xfb + (maxYpixel - yfb) * (maxXpixel + 1); // flip y coordinate (0,0 is bottom left in PS but top left in framebuffer)
framebuffer[idx] = fast_color_scaleAdd(framebuffer[idx], renderbuffer[xrb + yrb * 10]);
framebuffer[idx] = fast_color_add(framebuffer[idx], renderbuffer[xrb + yrb * 10]);
}
}
} else { // standard rendering (2x2 pixels)
@@ -784,7 +785,7 @@ void WLED_O2_ATTR ParticleSystem2D::renderParticle(const uint32_t particleindex,
for (uint32_t i = 0; i < 4; i++) {
if (pixelvalid[i]) {
uint32_t idx = pixco[i].x + (maxYpixel - pixco[i].y) * (maxXpixel + 1); // flip y coordinate (0,0 is bottom left in PS but top left in framebuffer)
framebuffer[idx] = fast_color_scaleAdd(framebuffer[idx], color, pxlbrightness[i]); // order is: bottom left, bottom right, top right, top left
framebuffer[idx] = fast_color_add(framebuffer[idx], color, pxlbrightness[i]); // order is: bottom left, bottom right, top right, top left
}
}
}
@@ -856,7 +857,7 @@ void ParticleSystem2D::handleCollisions() {
// handle a collision if close proximity is detected, i.e. dx and/or dy smaller than 2*PS_P_RADIUS
// takes two pointers to the particles to collide and the particle hardness (softer means more energy lost in collision, 255 means full hard)
void WLED_O2_ATTR ParticleSystem2D::collideParticles(PSparticle &particle1, PSparticle &particle2, int32_t dx, int32_t dy, const uint32_t collDistSq) {
__attribute__((optimize("O2"))) void ParticleSystem2D::collideParticles(PSparticle &particle1, PSparticle &particle2, int32_t dx, int32_t dy, const uint32_t collDistSq) {
int32_t distanceSquared = dx * dx + dy * dy;
// Calculate relative velocity note: could zero check but that does not improve overall speed but deminish it as that is rarely the case and pushing is still required
int32_t relativeVx = (int32_t)particle2.vx - (int32_t)particle1.vx;
@@ -1027,8 +1028,9 @@ void blur2D(uint32_t *colorbuffer, uint32_t xsize, uint32_t ysize, uint32_t xblu
for (uint32_t x = xstart; x < xstart + xsize; x++) {
seeppart = fast_color_scale(colorbuffer[indexXY], seep); // scale it and seep to neighbours
if (x > 0) {
colorbuffer[indexXY - 1] = fast_color_scaleAdd(colorbuffer[indexXY - 1], seeppart);
colorbuffer[indexXY] = fast_color_scaleAdd(colorbuffer[indexXY], carryover);
colorbuffer[indexXY - 1] = fast_color_add(colorbuffer[indexXY - 1], seeppart);
if (carryover.color32) // note: check adds overhead but is faster on average
colorbuffer[indexXY] = fast_color_add(colorbuffer[indexXY], carryover);
}
carryover = seeppart;
indexXY++; // next pixel in x direction
@@ -1047,8 +1049,9 @@ void blur2D(uint32_t *colorbuffer, uint32_t xsize, uint32_t ysize, uint32_t xblu
for (uint32_t y = ystart; y < ystart + ysize; y++) {
seeppart = fast_color_scale(colorbuffer[indexXY], seep); // scale it and seep to neighbours
if (y > 0) {
colorbuffer[indexXY - width] = fast_color_scaleAdd(colorbuffer[indexXY - width], seeppart);
colorbuffer[indexXY] = fast_color_scaleAdd(colorbuffer[indexXY], carryover);
colorbuffer[indexXY - width] = fast_color_add(colorbuffer[indexXY - width], seeppart);
if (carryover.color32) // note: check adds overhead but is faster on average
colorbuffer[indexXY] = fast_color_add(colorbuffer[indexXY], carryover);
}
carryover = seeppart;
indexXY += width; // next pixel in y direction
@@ -1467,7 +1470,7 @@ void ParticleSystem1D::render() {
CRGBW bg_color = SEGCOLOR(1);
if (bg_color > 0) { //if not black
for (int32_t i = 0; i <= maxXpixel; i++) {
framebuffer[i] = fast_color_scaleAdd(framebuffer[i], bg_color);
framebuffer[i] = fast_color_add(framebuffer[i], bg_color);
}
}
#ifndef WLED_DISABLE_2D
@@ -1482,7 +1485,7 @@ void ParticleSystem1D::render() {
}
// calculate pixel positions and brightness distribution and render the particle to local buffer or global buffer
void WLED_O2_ATTR ParticleSystem1D::renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGBW &color, const bool wrap) {
__attribute__((optimize("O2"))) void ParticleSystem1D::renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGBW &color, const bool wrap) {
uint32_t size = particlesize;
if (advPartProps) // use advanced size properties (1D system has no large size global rendering TODO: add large global rendering?)
size = advPartProps[particleindex].size;
@@ -1490,7 +1493,7 @@ void WLED_O2_ATTR ParticleSystem1D::renderParticle(const uint32_t particleindex,
if (size == 0) { //single pixel particle, can be out of bounds as oob checking is made for 2-pixel particles (and updating it uses more code)
uint32_t x = particles[particleindex].x >> PS_P_RADIUS_SHIFT_1D;
if (x <= (uint32_t)maxXpixel) { //by making x unsigned there is no need to check < 0 as it will overflow
framebuffer[x] = fast_color_scaleAdd(framebuffer[x], color, brightness);
framebuffer[x] = fast_color_add(framebuffer[x], color, brightness);
}
return;
}
@@ -1527,8 +1530,8 @@ void WLED_O2_ATTR ParticleSystem1D::renderParticle(const uint32_t particleindex,
//render particle to a bigger size
//particle size to pixels: 2 - 63 is 4 pixels, < 128 is 6pixels, < 192 is 8 pixels, bigger is 10 pixels
//first, render the pixel to the center of the renderbuffer, then apply 1D blurring
renderbuffer[4] = fast_color_scaleAdd(renderbuffer[4], color, pxlbrightness[0]);
renderbuffer[5] = fast_color_scaleAdd(renderbuffer[5], color, pxlbrightness[1]);
renderbuffer[4] = fast_color_add(renderbuffer[4], color, pxlbrightness[0]);
renderbuffer[5] = fast_color_add(renderbuffer[5], color, pxlbrightness[1]);
uint32_t rendersize = 2; // initialize render size, minimum is 4 pixels, it is incremented int he loop below to start with 4
uint32_t offset = 4; // offset to zero coordinate to write/read data in renderbuffer (actually needs to be 3, is decremented in the loop below)
uint32_t blurpasses = size/64 + 1; // number of blur passes depends on size, four passes max
@@ -1562,7 +1565,7 @@ void WLED_O2_ATTR ParticleSystem1D::renderParticle(const uint32_t particleindex,
#ifdef ESP8266 // no local buffer on ESP8266
SEGMENT.addPixelColor(xfb, renderbuffer[xrb], true);
#else
framebuffer[xfb] = fast_color_scaleAdd(framebuffer[xfb], renderbuffer[xrb]);
framebuffer[xfb] = fast_color_add(framebuffer[xfb], renderbuffer[xrb]);
#endif
}
}
@@ -1582,7 +1585,7 @@ void WLED_O2_ATTR ParticleSystem1D::renderParticle(const uint32_t particleindex,
}
for (uint32_t i = 0; i < 2; i++) {
if (pxlisinframe[i]) {
framebuffer[pixco[i]] = fast_color_scaleAdd(framebuffer[pixco[i]], color, pxlbrightness[i]);
framebuffer[pixco[i]] = fast_color_add(framebuffer[pixco[i]], color, pxlbrightness[i]);
}
}
}
@@ -1645,7 +1648,7 @@ void ParticleSystem1D::handleCollisions() {
}
// handle a collision if close proximity is detected, i.e. dx and/or dy smaller than 2*PS_P_RADIUS
// takes two pointers to the particles to collide and the particle hardness (softer means more energy lost in collision, 255 means full hard)
void WLED_O2_ATTR ParticleSystem1D::collideParticles(PSparticle1D &particle1, const PSparticleFlags1D &particle1flags, PSparticle1D &particle2, const PSparticleFlags1D &particle2flags, const int32_t dx, const uint32_t dx_abs, const uint32_t collisiondistance) {
__attribute__((optimize("O2"))) void ParticleSystem1D::collideParticles(PSparticle1D &particle1, const PSparticleFlags1D &particle1flags, PSparticle1D &particle2, const PSparticleFlags1D &particle2flags, const int32_t dx, const uint32_t dx_abs, const uint32_t collisiondistance) {
int32_t dv = particle2.vx - particle1.vx;
int32_t dotProduct = (dx * dv); // is always negative if moving towards each other
@@ -1834,8 +1837,9 @@ void blur1D(uint32_t *colorbuffer, uint32_t size, uint32_t blur, uint32_t start)
for (uint32_t x = start; x < start + size; x++) {
seeppart = fast_color_scale(colorbuffer[x], seep); // scale it and seep to neighbours
if (x > 0) {
colorbuffer[x-1] = fast_color_scaleAdd(colorbuffer[x-1], seeppart);
colorbuffer[x] = fast_color_scaleAdd(colorbuffer[x], carryover); // is black on first pass
colorbuffer[x-1] = fast_color_add(colorbuffer[x-1], seeppart);
if (carryover.color32) // note: check adds overhead but is faster on average
colorbuffer[x] = fast_color_add(colorbuffer[x], carryover); // is black on first pass
}
carryover = seeppart;
}
@@ -1884,34 +1888,36 @@ static bool checkBoundsAndWrap(int32_t &position, const int32_t max, const int32
return true; // particle is in bounds
}
// this is a fast version for RGB color adding ignoring white channel (PS does not handle white) including scaling of second color
// this is a fast version for CRGBW color adding ignoring white channel (PS does not handle white) including scaling of second color
// note: function is mainly used to add scaled colors, so checking if one color is black is slower
static uint32_t fast_color_scaleAdd(const uint32_t c1, const uint32_t c2, const uint8_t scale) {
constexpr uint32_t MASK_RB = 0x00FF00FF; // red and blue mask
constexpr uint32_t MASK_G = 0x0000FF00; // green mask
// note2: returning CRGBW value is slightly slower as the return value gets written to uint32_t framebuffer
__attribute__((optimize("O2"))) static uint32_t fast_color_add(CRGBW c1, const CRGBW c2, const uint8_t scale) {
uint32_t r, g, b;
r = c1.r + ((c2.r * scale) >> 8);
g = c1.g + ((c2.g * scale) >> 8);
b = c1.b + ((c2.b * scale) >> 8);
uint32_t rb = c2 & MASK_RB; // 0x00RR00BB
uint32_t g = c2 & MASK_G; // 0x0000GG00
// scale second color
rb = ((rb * scale) >> 8) & MASK_RB;
g = ((g * scale) >> 8) & MASK_G;
// add colors
rb = (c1 & MASK_RB) + rb;
g = ((c1 & MASK_G) + g);
// note: this chained comparison is the fastest method for max of 3 values (faster than std:max() or using xor)
uint32_t max = (r > g) ? ((r > b) ? r : b) : ((g > b) ? g : b);
if (max <= 255) {
c1.r = r; // save result to c1
c1.g = g;
c1.b = b;
} else {
uint32_t newscale = (255U << 16) / max;
c1.r = (r * newscale) >> 16;
c1.g = (g * newscale) >> 16;
c1.b = (b * newscale) >> 16;
}
return c1.color32;
}
// check for overflow by looking at the 9th bit of each channel
if ((rb | (g >> 8)) & 0x01000100) {
// find max among the three 16-bit values
g = g >> 8; // shift to get 0x000000GG
uint32_t max_val = (rb >> 16); // red
max_val = ((rb & 0xFFFF) > max_val) ? rb & 0xFFFF : max_val; // blue
max_val = (g > max_val) ? g : max_val; // green
// scale down to avoid saturation
uint32_t scale_factor = (255 << 8) / max_val;
rb = ((rb * scale_factor) >> 8) & MASK_RB;
g = (g * scale_factor) & MASK_G;
}
return rb | g;
// fast CRGBW color scaling ignoring white channel (PS does not handle white)
__attribute__((optimize("O2"))) static uint32_t fast_color_scale(CRGBW c, const uint8_t scale) {
c.r = ((c.r * scale) >> 8);
c.g = ((c.g * scale) >> 8);
c.b = ((c.b * scale) >> 8);
return c.color32;
}
#endif // !(defined(WLED_DISABLE_PARTICLESYSTEM2D) && defined(WLED_DISABLE_PARTICLESYSTEM1D))

View File

@@ -32,36 +32,6 @@ extern char cmDNS[];
extern bool cctICused;
extern bool useParallelI2S;
// functions to get/set bits in an array - based on functions created by Brandon for GOL
// toDo : make this a class that's completely defined in a header file
bool getBitFromArray(const uint8_t* byteArray, size_t position) { // get bit value
size_t byteIndex = position / 8;
unsigned bitIndex = position % 8;
uint8_t byteValue = byteArray[byteIndex];
return (byteValue >> bitIndex) & 1;
}
void setBitInArray(uint8_t* byteArray, size_t position, bool value) { // set bit - with error handling for nullptr
//if (byteArray == nullptr) return;
size_t byteIndex = position / 8;
unsigned bitIndex = position % 8;
if (value)
byteArray[byteIndex] |= (1 << bitIndex);
else
byteArray[byteIndex] &= ~(1 << bitIndex);
}
size_t getBitArrayBytes(size_t num_bits) { // number of bytes needed for an array with num_bits bits
return (num_bits + 7) / 8;
}
void setBitArray(uint8_t* byteArray, size_t numBits, bool value) { // set all bits to same value
if (byteArray == nullptr) return;
size_t len = getBitArrayBytes(numBits);
if (value) memset(byteArray, 0xFF, len);
else memset(byteArray, 0x00, len);
}
//colors.cpp
uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
@@ -69,29 +39,35 @@ uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, const byte *buffer, uint8_t bri=255, bool isRGBW=false);
//util.cpp
// memory allocation wrappers
// PSRAM allocation wrappers
#if !defined(ESP8266) && !defined(CONFIG_IDF_TARGET_ESP32C3)
extern "C" {
// prefer DRAM over PSRAM (if available) in d_ alloc functions
void *d_malloc(size_t);
void *d_calloc(size_t, size_t);
void *d_realloc_malloc(void *ptr, size_t size);
#ifndef ESP8266
inline void d_free(void *ptr) { heap_caps_free(ptr); }
#else
inline void d_free(void *ptr) { free(ptr); }
#endif
#if defined(BOARD_HAS_PSRAM)
// prefer PSRAM over DRAM in p_ alloc functions
void *p_malloc(size_t);
void *p_calloc(size_t, size_t);
void *p_realloc_malloc(void *ptr, size_t size);
void *p_malloc(size_t); // prefer PSRAM over DRAM
void *p_calloc(size_t, size_t); // prefer PSRAM over DRAM
void *p_realloc(void *, size_t); // prefer PSRAM over DRAM
void *p_realloc_malloc(void *ptr, size_t size); // realloc with malloc fallback, prefer PSRAM over DRAM
inline void p_free(void *ptr) { heap_caps_free(ptr); }
#else
#define p_malloc d_malloc
#define p_calloc d_calloc
#define p_free d_free
#endif
void *d_malloc(size_t); // prefer DRAM over PSRAM
void *d_calloc(size_t, size_t); // prefer DRAM over PSRAM
void *d_realloc(void *, size_t); // prefer DRAM over PSRAM
void *d_realloc_malloc(void *ptr, size_t size); // realloc with malloc fallback, prefer DRAM over PSRAM
inline void d_free(void *ptr) { heap_caps_free(ptr); }
}
#else
extern "C" {
void *realloc_malloc(void *ptr, size_t size);
}
#define p_malloc malloc
#define p_calloc calloc
#define p_realloc realloc
#define p_realloc_malloc realloc_malloc
#define p_free free
#define d_malloc malloc
#define d_calloc calloc
#define d_realloc realloc
#define d_realloc_malloc realloc_malloc
#define d_free free
#endif
//color mangling macros
#define RGBW32(r,g,b,w) (uint32_t((byte(w) << 24) | (byte(r) << 16) | (byte(g) << 8) | (byte(b))))
@@ -233,7 +209,6 @@ void BusDigital::estimateCurrent() {
void BusDigital::applyBriLimit(uint8_t newBri) {
// a newBri of 0 means calculate per-bus brightness limit
_NPBbri = 255; // reset, intermediate value is set below, final value is calculated in bus::show()
if (newBri == 0) {
if (_milliAmpsLimit == 0 || _milliAmpsTotal == 0) return; // ABL not used for this bus
newBri = 255;
@@ -251,7 +226,6 @@ void BusDigital::applyBriLimit(uint8_t newBri) {
}
if (newBri < 255) {
_NPBbri = newBri; // store value so it can be updated in show() (must be updated even if ABL is not used)
uint8_t cctWW = 0, cctCW = 0;
unsigned hwLen = _len;
if (_type == TYPE_WS2812_1CH_X3) hwLen = NUM_ICS_WS2812_1CH_3X(_len); // only needs a third of "RGB" LEDs for NeoPixelBus
@@ -269,7 +243,6 @@ void BusDigital::applyBriLimit(uint8_t newBri) {
void BusDigital::show() {
if (!_valid) return;
_NPBbri = (_NPBbri * _bri) / 255; // total applied brightness for use in restoreColorLossy (see applyBriLimit())
PolyBus::show(_busPtr, _iType, _skip); // faster if buffer consistency is not important (no skipped LEDs)
}
@@ -332,7 +305,7 @@ uint32_t IRAM_ATTR BusDigital::getPixelColor(unsigned pix) const {
if (_reversed) pix = _len - pix -1;
pix += _skip;
const uint8_t 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),_NPBbri);
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
uint8_t r = R(c);
uint8_t g = _reversed ? B(c) : G(c); // should G and B be switched if _reversed?
@@ -357,7 +330,7 @@ size_t BusDigital::getPins(uint8_t* pinArray) const {
}
size_t BusDigital::getBusSize() const {
return sizeof(BusDigital) + (isOk() ? PolyBus::getDataSize(_busPtr, _iType) : 0); // does not include common I2S DMA buffer
return sizeof(BusDigital) + (isOk() ? PolyBus::getDataSize(_busPtr, _iType) : 0);
}
void BusDigital::setColorOrder(uint8_t colorOrder) {
@@ -787,347 +760,13 @@ void BusNetwork::cleanup() {
_valid = false;
}
// ***************************************************************************
#ifdef WLED_ENABLE_HUB75MATRIX
#warning "HUB75 driver enabled (experimental)"
#ifdef ESP8266
#error ESP8266 does not support HUB75
#endif
BusHub75Matrix::BusHub75Matrix(const BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) {
size_t lastHeap = ESP.getFreeHeap();
_valid = false;
_hasRgb = true;
_hasWhite = false;
mxconfig.double_buff = false; // Use our own memory-optimised buffer rather than the driver's own double-buffer
// mxconfig.driver = HUB75_I2S_CFG::ICN2038S; // experimental - use specific shift register driver
// mxconfig.driver = HUB75_I2S_CFG::FM6124; // try this driver in case you panel stays dark, or when colors look too pastel
// mxconfig.latch_blanking = 3;
// mxconfig.i2sspeed = HUB75_I2S_CFG::HZ_10M; // experimental - 5MHZ should be enugh, but colours looks slightly better at 10MHz
//mxconfig.min_refresh_rate = 90;
//mxconfig.min_refresh_rate = 120;
mxconfig.clkphase = bc.reversed;
virtualDisp = nullptr;
if (bc.type == TYPE_HUB75MATRIX_HS) {
mxconfig.mx_width = min((uint8_t) 64, bc.pins[0]);
mxconfig.mx_height = min((uint8_t) 64, bc.pins[1]);
// Disable chains of panels for now, incomplete UI changes
// if(bc.pins[2] > 1 && bc.pins[3] != 0 && bc.pins[4] != 0 && bc.pins[3] != 255 && bc.pins[4] != 255) {
// virtualDisp = new VirtualMatrixPanel((*display), bc.pins[3], bc.pins[4], mxconfig.mx_width, mxconfig.mx_height, CHAIN_BOTTOM_LEFT_UP);
// }
} else if (bc.type == TYPE_HUB75MATRIX_QS) {
mxconfig.mx_width = min((uint8_t) 64, bc.pins[0]) * 2;
mxconfig.mx_height = min((uint8_t) 64, bc.pins[1]) / 2;
virtualDisp = new VirtualMatrixPanel((*display), 1, 1, bc.pins[0], bc.pins[1]);
virtualDisp->setRotation(0);
switch(bc.pins[1]) {
case 16:
virtualDisp->setPhysicalPanelScanRate(FOUR_SCAN_16PX_HIGH);
break;
case 32:
virtualDisp->setPhysicalPanelScanRate(FOUR_SCAN_32PX_HIGH);
break;
case 64:
virtualDisp->setPhysicalPanelScanRate(FOUR_SCAN_64PX_HIGH);
break;
default:
DEBUGBUS_PRINTLN("Unsupported height");
return;
}
} else {
DEBUGBUS_PRINTLN("Unknown type");
return;
}
#if defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S2)// classic esp32, or esp32-s2: reduce bitdepth for large panels
if (mxconfig.mx_height >= 64) {
if (mxconfig.chain_length * mxconfig.mx_width > 192) mxconfig.setPixelColorDepthBits(3);
else if (mxconfig.chain_length * mxconfig.mx_width > 64) mxconfig.setPixelColorDepthBits(4);
else mxconfig.setPixelColorDepthBits(8);
} else mxconfig.setPixelColorDepthBits(8);
#endif
mxconfig.chain_length = max((uint8_t) 1, min(bc.pins[2], (uint8_t) 4)); // prevent bad data preventing boot due to low memory
if(mxconfig.mx_height >= 64 && (mxconfig.chain_length > 1)) {
DEBUGBUS_PRINTLN("WARNING, only single panel can be used of 64 pixel boards due to memory");
mxconfig.chain_length = 1;
}
// HUB75_I2S_CFG::i2s_pins _pins={R1_PIN, G1_PIN, B1_PIN, R2_PIN, G2_PIN, B2_PIN, A_PIN, B_PIN, C_PIN, D_PIN, E_PIN, LAT_PIN, OE_PIN, CLK_PIN};
#if defined(ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3) // MatrixPortal ESP32-S3
// https://www.adafruit.com/product/5778
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA - Matrix Portal S3 config");
mxconfig.gpio = { 42, 41, 40, 38, 39, 37, 45, 36, 48, 35, 21, 47, 14, 2 };
#elif defined(CONFIG_IDF_TARGET_ESP32S3) && defined(BOARD_HAS_PSRAM)// ESP32-S3 with PSRAM
#if defined(MOONHUB_S3_PINOUT)
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA - T7 S3 with PSRAM, MOONHUB pinout");
// HUB75_I2S_CFG::i2s_pins _pins={R1_PIN, G1_PIN, B1_PIN, R2_PIN, G2_PIN, B2_PIN, A_PIN, B_PIN, C_PIN, D_PIN, E_PIN, LAT_PIN, OE_PIN, CLK_PIN};
mxconfig.gpio = { 1, 5, 6, 7, 13, 9, 16, 48, 47, 21, 38, 8, 4, 18 };
#else
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA - S3 with PSRAM");
// HUB75_I2S_CFG::i2s_pins _pins={R1_PIN, G1_PIN, B1_PIN, R2_PIN, G2_PIN, B2_PIN, A_PIN, B_PIN, C_PIN, D_PIN, E_PIN, LAT_PIN, OE_PIN, CLK_PIN};
mxconfig.gpio = {1, 2, 42, 41, 40, 39, 45, 48, 47, 21, 38, 8, 3, 18};
#endif
#elif defined(ESP32_FORUM_PINOUT) // Common format for boards designed for SmartMatrix
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA - ESP32_FORUM_PINOUT");
/*
ESP32 with SmartMatrix's default pinout - ESP32_FORUM_PINOUT
https://github.com/pixelmatix/SmartMatrix/blob/teensylc/src/MatrixHardware_ESP32_V0.h
Can use a board like https://github.com/rorosaurus/esp32-hub75-driver
*/
mxconfig.gpio = { 2, 15, 4, 16, 27, 17, 5, 18, 19, 21, 12, 26, 25, 22 };
#else
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA - Default pins");
/*
https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA?tab=readme-ov-file
Boards
https://esp32trinity.com/
https://www.electrodragon.com/product/rgb-matrix-panel-drive-interface-board-for-esp32-dma/
*/
mxconfig.gpio = { 25, 26, 27, 14, 12, 13, 23, 19, 5, 17, 18, 4, 15, 16 };
#endif
int8_t pins[PIN_COUNT];
memcpy(pins, &mxconfig.gpio, sizeof(mxconfig.gpio));
if (!PinManager::allocateMultiplePins(pins, PIN_COUNT, PinOwner::HUB75, true)) {
DEBUGBUS_PRINTLN("Failed to allocate pins for HUB75");
return;
}
if(bc.colorOrder == COL_ORDER_RGB) {
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA = Default color order (RGB)");
} else if(bc.colorOrder == COL_ORDER_BGR) {
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA = color order BGR");
int8_t tmpPin;
tmpPin = mxconfig.gpio.r1;
mxconfig.gpio.r1 = mxconfig.gpio.b1;
mxconfig.gpio.b1 = tmpPin;
tmpPin = mxconfig.gpio.r2;
mxconfig.gpio.r2 = mxconfig.gpio.b2;
mxconfig.gpio.b2 = tmpPin;
}
else {
DEBUGBUS_PRINTF("MatrixPanel_I2S_DMA = unsupported color order %u\n", bc.colorOrder);
}
DEBUGBUS_PRINTF("MatrixPanel_I2S_DMA config - %ux%u length: %u\n", mxconfig.mx_width, mxconfig.mx_height, mxconfig.chain_length);
DEBUGBUS_PRINTF("R1_PIN=%u, G1_PIN=%u, B1_PIN=%u, R2_PIN=%u, G2_PIN=%u, B2_PIN=%u, A_PIN=%u, B_PIN=%u, C_PIN=%u, D_PIN=%u, E_PIN=%u, LAT_PIN=%u, OE_PIN=%u, CLK_PIN=%u\n",
mxconfig.gpio.r1, mxconfig.gpio.g1, mxconfig.gpio.b1, mxconfig.gpio.r2, mxconfig.gpio.g2, mxconfig.gpio.b2,
mxconfig.gpio.a, mxconfig.gpio.b, mxconfig.gpio.c, mxconfig.gpio.d, mxconfig.gpio.e, mxconfig.gpio.lat, mxconfig.gpio.oe, mxconfig.gpio.clk);
// OK, now we can create our matrix object
display = new MatrixPanel_I2S_DMA(mxconfig);
if (display == nullptr) {
DEBUGBUS_PRINTLN("****** MatrixPanel_I2S_DMA !KABOOM! driver allocation failed ***********");
DEBUGBUS_PRINT(F("heap usage: ")); DEBUGBUS_PRINTLN(lastHeap - ESP.getFreeHeap());
return;
}
this->_len = (display->width() * display->height());
DEBUGBUS_PRINTF("Length: %u\n", _len);
if(this->_len >= MAX_LEDS) {
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA Too many LEDS - playing safe");
return;
}
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA created");
// let's adjust default brightness
display->setBrightness8(25); // range is 0-255, 0 - 0%, 255 - 100%
delay(24); // experimental
DEBUGBUS_PRINT(F("heap usage: ")); DEBUGBUS_PRINTLN(lastHeap - ESP.getFreeHeap());
// Allocate memory and start DMA display
if( not display->begin() ) {
DEBUGBUS_PRINTLN("****** MatrixPanel_I2S_DMA !KABOOM! I2S memory allocation failed ***********");
DEBUGBUS_PRINT(F("heap usage: ")); DEBUGBUS_PRINTLN(lastHeap - ESP.getFreeHeap());
return;
}
else {
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA begin ok");
DEBUGBUS_PRINT(F("heap usage: ")); DEBUGBUS_PRINTLN(lastHeap - ESP.getFreeHeap());
delay(18); // experiment - give the driver a moment (~ one full frame @ 60hz) to settle
_valid = true;
display->clearScreen(); // initially clear the screen buffer
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA clear ok");
if (_ledBuffer) free(_ledBuffer); // should not happen
if (_ledsDirty) free(_ledsDirty); // should not happen
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA allocate memory");
_ledsDirty = (byte*) malloc(getBitArrayBytes(_len)); // create LEDs dirty bits
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA allocate memory ok");
if (_ledsDirty == nullptr) {
display->stopDMAoutput();
delete display; display = nullptr;
_valid = false;
DEBUGBUS_PRINTLN(F("MatrixPanel_I2S_DMA not started - not enough memory for dirty bits!"));
DEBUGBUS_PRINT(F("heap usage: ")); DEBUGBUS_PRINTLN(lastHeap - ESP.getFreeHeap());
return; // fail is we cannot get memory for the buffer
}
setBitArray(_ledsDirty, _len, false); // reset dirty bits
if (mxconfig.double_buff == false) {
_ledBuffer = (CRGB*) calloc(_len, sizeof(CRGB)); // create LEDs buffer (initialized to BLACK)
}
}
if (_valid) {
_panelWidth = virtualDisp ? virtualDisp->width() : display->width(); // cache width - it will never change
}
DEBUGBUS_PRINT(F("MatrixPanel_I2S_DMA "));
DEBUGBUS_PRINTF("%sstarted, width=%u, %u pixels.\n", _valid? "":"not ", _panelWidth, _len);
if (_ledBuffer != nullptr) DEBUGBUS_PRINTLN(F("MatrixPanel_I2S_DMA LEDS buffer enabled."));
if (_ledsDirty != nullptr) DEBUGBUS_PRINTLN(F("MatrixPanel_I2S_DMA LEDS dirty bit optimization enabled."));
if ((_ledBuffer != nullptr) || (_ledsDirty != nullptr)) {
DEBUGBUS_PRINT(F("MatrixPanel_I2S_DMA LEDS buffer uses "));
DEBUGBUS_PRINT((_ledBuffer? _len*sizeof(CRGB) :0) + (_ledsDirty? getBitArrayBytes(_len) :0));
DEBUGBUS_PRINTLN(F(" bytes."));
}
}
void __attribute__((hot)) BusHub75Matrix::setPixelColor(unsigned pix, uint32_t c) {
if (!_valid || pix >= _len) return;
// if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT
if (_ledBuffer) {
CRGB fastled_col = CRGB(c);
if (_ledBuffer[pix] != fastled_col) {
_ledBuffer[pix] = fastled_col;
setBitInArray(_ledsDirty, pix, true); // flag pixel as "dirty"
}
}
else {
if ((c == IS_BLACK) && (getBitFromArray(_ledsDirty, pix) == false)) return; // ignore black if pixel is already black
setBitInArray(_ledsDirty, pix, c != IS_BLACK); // dirty = true means "color is not BLACK"
uint8_t r = R(c);
uint8_t g = G(c);
uint8_t b = B(c);
if(virtualDisp != nullptr) {
int x = pix % _panelWidth;
int y = pix / _panelWidth;
virtualDisp->drawPixelRGB888(int16_t(x), int16_t(y), r, g, b);
} else {
int x = pix % _panelWidth;
int y = pix / _panelWidth;
display->drawPixelRGB888(int16_t(x), int16_t(y), r, g, b);
}
}
}
uint32_t BusHub75Matrix::getPixelColor(unsigned pix) const {
if (!_valid || pix >= _len) return IS_BLACK;
if (_ledBuffer)
return uint32_t(_ledBuffer[pix].scale8(_bri)) & 0x00FFFFFF; // scale8() is needed to mimic NeoPixelBus, which returns scaled-down colours
else
return getBitFromArray(_ledsDirty, pix) ? IS_DARKGREY: IS_BLACK; // just a hack - we only know if the pixel is black or not
}
void BusHub75Matrix::setBrightness(uint8_t b) {
_bri = b;
if (display) display->setBrightness(_bri);
}
void BusHub75Matrix::show(void) {
if (!_valid) return;
display->setBrightness(_bri);
if (_ledBuffer) {
// write out buffered LEDs
bool isVirtualDisp = (virtualDisp != nullptr);
unsigned height = isVirtualDisp ? virtualDisp->height() : display->height();
unsigned width = _panelWidth;
//while(!previousBufferFree) delay(1); // experimental - Wait before we allow any writing to the buffer. Stop flicker.
size_t pix = 0; // running pixel index
for (int y=0; y<height; y++) for (int x=0; x<width; x++) {
if (getBitFromArray(_ledsDirty, pix) == true) { // only repaint the "dirty" pixels
uint32_t c = uint32_t(_ledBuffer[pix]) & 0x00FFFFFF; // get RGB color, removing FastLED "alpha" component
uint8_t r = R(c);
uint8_t g = G(c);
uint8_t b = B(c);
if (isVirtualDisp) virtualDisp->drawPixelRGB888(int16_t(x), int16_t(y), r, g, b);
else display->drawPixelRGB888(int16_t(x), int16_t(y), r, g, b);
}
pix ++;
}
setBitArray(_ledsDirty, _len, false); // buffer shown - reset all dirty bits
}
}
void BusHub75Matrix::cleanup() {
if (display && _valid) display->stopDMAoutput(); // terminate DMA driver (display goes black)
_valid = false;
_panelWidth = 0;
deallocatePins();
DEBUGBUS_PRINTLN("HUB75 output ended.");
//if (virtualDisp != nullptr) delete virtualDisp; // warning: deleting object of polymorphic class type 'VirtualMatrixPanel' which has non-virtual destructor might cause undefined behavior
delete display;
display = nullptr;
virtualDisp = nullptr;
if (_ledBuffer != nullptr) free(_ledBuffer); _ledBuffer = nullptr;
if (_ledsDirty != nullptr) free(_ledsDirty); _ledsDirty = nullptr;
}
void BusHub75Matrix::deallocatePins() {
uint8_t pins[PIN_COUNT];
memcpy(pins, &mxconfig.gpio, sizeof(mxconfig.gpio));
PinManager::deallocateMultiplePins(pins, PIN_COUNT, PinOwner::HUB75);
}
std::vector<LEDType> BusHub75Matrix::getLEDTypes() {
return {
{TYPE_HUB75MATRIX_HS, "H", PSTR("HUB75 (Half Scan)")},
{TYPE_HUB75MATRIX_QS, "H", PSTR("HUB75 (Quarter Scan)")},
};
}
size_t BusHub75Matrix::getPins(uint8_t* pinArray) const {
if (pinArray) {
pinArray[0] = mxconfig.mx_width;
pinArray[1] = mxconfig.mx_height;
pinArray[2] = mxconfig.chain_length;
}
return 3;
}
#endif
// ***************************************************************************
//utility to get the approx. memory usage of a given BusConfig
size_t BusConfig::memUsage(unsigned nr) const {
if (Bus::isVirtual(type)) {
return sizeof(BusNetwork) + (count * Bus::getNumberOfChannels(type));
} else if (Bus::isDigital(type)) {
// if any of digital buses uses I2S, there is additional common I2S DMA buffer not accounted for here
return sizeof(BusDigital) + PolyBus::memUsage(count + skipAmount, PolyBus::getI(type, pins, nr));
return sizeof(BusDigital) + PolyBus::memUsage(count + skipAmount, PolyBus::getI(type, pins, nr)) /*+ doubleBuffer * (count + skipAmount) * Bus::getNumberOfChannels(type)*/;
} else if (Bus::isOnOff(type)) {
return sizeof(BusOnOff);
} else {
@@ -1143,23 +782,23 @@ size_t BusManager::memUsage() {
unsigned maxI2S = 0;
#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(ESP8266)
unsigned digitalCount = 0;
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3)
#define MAX_RMT 4
#else
#define MAX_RMT 8
#endif
#endif
for (const auto &bus : busses) {
size += bus->getBusSize();
unsigned busSize = bus->getBusSize();
#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(ESP8266)
if (bus->isDigital() && !bus->is2Pin()) {
digitalCount++;
if ((PolyBus::isParallelI2S1Output() && digitalCount <= 8) || (!PolyBus::isParallelI2S1Output() && digitalCount == 1)) {
#ifdef NPB_CONF_4STEP_CADENCE
constexpr unsigned stepFactor = 4; // 4 step cadence (4 bits per pixel bit)
#else
constexpr unsigned stepFactor = 3; // 3 step cadence (3 bits per pixel bit)
#endif
unsigned i2sCommonSize = stepFactor * bus->getLength() * bus->getNumberOfChannels() * (bus->is16bit()+1);
if (i2sCommonSize > maxI2S) maxI2S = i2sCommonSize;
}
if (bus->isDigital() && !bus->is2Pin()) digitalCount++;
if (PolyBus::isParallelI2S1Output() && digitalCount > MAX_RMT) {
unsigned i2sCommonSize = 3 * bus->getLength() * bus->getNumberOfChannels() * (bus->is16bit()+1);
if (i2sCommonSize > maxI2S) maxI2S = i2sCommonSize;
busSize -= i2sCommonSize;
}
#endif
size += busSize;
}
return size + maxI2S;
}
@@ -1177,10 +816,6 @@ int BusManager::add(const BusConfig &bc) {
if (digital > WLED_MAX_DIGITAL_CHANNELS || analog > WLED_MAX_ANALOG_CHANNELS) return -1;
if (Bus::isVirtual(bc.type)) {
busses.push_back(make_unique<BusNetwork>(bc));
#ifdef WLED_ENABLE_HUB75MATRIX
} else if (Bus::isHub75(bc.type)) {
busses.push_back(make_unique<BusHub75Matrix>(bc));
#endif
} else if (Bus::isDigital(bc.type)) {
busses.push_back(make_unique<BusDigital>(bc, Bus::is2Pin(bc.type) ? twoPin : digital));
} else if (Bus::isOnOff(bc.type)) {
@@ -1212,10 +847,6 @@ String BusManager::getLEDTypesJSONString() {
json += LEDTypesToJson(BusPwm::getLEDTypes());
json += LEDTypesToJson(BusNetwork::getLEDTypes());
//json += LEDTypesToJson(BusVirtual::getLEDTypes());
#ifdef WLED_ENABLE_HUB75MATRIX
json += LEDTypesToJson(BusHub75Matrix::getLEDTypes());
#endif
json.setCharAt(json.length()-1, ']'); // replace last comma with bracket
return json;
}
@@ -1271,7 +902,7 @@ void BusManager::esp32RMTInvertIdle() {
else if (lvl == RMT_IDLE_LEVEL_LOW) lvl = RMT_IDLE_LEVEL_HIGH;
else continue;
rmt_set_idle_level(ch, idle_out, lvl);
u++;
u++
}
}
#endif

View File

@@ -2,13 +2,6 @@
#ifndef BusManager_h
#define BusManager_h
#ifdef WLED_ENABLE_HUB75MATRIX
#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
#include <ESP32-VirtualMatrixPanel-I2S-DMA.h>
#include <FastLED.h>
#endif
/*
* Class for addressing various light types
*/
@@ -112,7 +105,6 @@ class Bus {
Bus(uint8_t type, uint16_t start, uint8_t aw, uint16_t len = 1, bool reversed = false, bool refresh = false)
: _type(type)
, _bri(255)
, _NPBbri(255)
, _start(start)
, _len(std::max(len,(uint16_t)1))
, _reversed(reversed)
@@ -166,7 +158,7 @@ class Bus {
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 constexpr size_t getNumberOfPins(uint8_t type) { return isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : isHub75(type) ? 3 : is2Pin(type) + 1; } // credit @PaoloTK
static constexpr size_t getNumberOfPins(uint8_t type) { return isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : is2Pin(type) + 1; } // credit @PaoloTK
static constexpr size_t getNumberOfChannels(uint8_t type) { return hasWhite(type) + 3*hasRGB(type) + hasCCT(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);
@@ -190,7 +182,6 @@ class Bus {
static constexpr bool isOnOff(uint8_t type) { return (type == TYPE_ONOFF); }
static constexpr bool isPWM(uint8_t type) { return (type >= TYPE_ANALOG_MIN && type <= TYPE_ANALOG_MAX); }
static constexpr bool isVirtual(uint8_t type) { return (type >= TYPE_VIRTUAL_MIN && type <= TYPE_VIRTUAL_MAX); }
static constexpr bool isHub75(uint8_t type) { return (type >= TYPE_HUB75MATRIX_MIN && type <= TYPE_HUB75MATRIX_MAX); }
static constexpr bool is16bit(uint8_t type) { return type == TYPE_UCS8903 || type == TYPE_UCS8904 || type == TYPE_SM16825; }
static constexpr bool mustRefresh(uint8_t type) { return type == TYPE_TM1814; }
static constexpr int numPWMPins(uint8_t type) { return (type - 40); }
@@ -211,9 +202,7 @@ class Bus {
protected:
uint8_t _type;
uint8_t _bri; // bus brightness
uint8_t _NPBbri; // total brightness applied to colors in NPB buffer (_bri + ABL)
uint8_t _autoWhiteMode; // global Auto White Calculation override
uint8_t _bri;
uint16_t _start;
uint16_t _len;
//struct { //using bitfield struct adds abour 250 bytes to binary size
@@ -224,6 +213,8 @@ class Bus {
bool _hasWhite;// : 1;
bool _hasCCT;// : 1;
//} __attribute__ ((packed));
uint8_t _autoWhiteMode;
// global Auto White Calculation override
static uint8_t _gAWM;
// _cct has the following meanings (see calculateCCT() & BusManager::setSegmentCCT()):
// -1 means to extract approximate CCT value in K from RGB (in calcualteCCT())
@@ -372,37 +363,6 @@ class BusNetwork : public Bus {
#endif
};
#ifdef WLED_ENABLE_HUB75MATRIX
class BusHub75Matrix : public Bus {
public:
BusHub75Matrix(const BusConfig &bc);
[[gnu::hot]] void setPixelColor(unsigned pix, uint32_t c) override;
[[gnu::hot]] uint32_t getPixelColor(unsigned pix) const override;
void show() override;
void setBrightness(uint8_t b) override;
size_t getPins(uint8_t* pinArray = nullptr) const override;
void deallocatePins();
void cleanup();
~BusHub75Matrix() {
cleanup();
}
static std::vector<LEDType> getLEDTypes(void);
private:
MatrixPanel_I2S_DMA *display = nullptr;
VirtualMatrixPanel *virtualDisp = nullptr;
HUB75_I2S_CFG mxconfig;
unsigned _panelWidth = 0;
CRGB *_ledBuffer = nullptr;
byte *_ledsDirty = nullptr;
// workaround for missing constants on include path for non-MM
uint32_t IS_BLACK = 0x000000;
uint32_t IS_DARKGREY = 0x333333;
const int PIN_COUNT = 14;
};
#endif
//temporary struct for passing bus configuration to bus
struct BusConfig {
@@ -414,7 +374,7 @@ struct BusConfig {
uint8_t skipAmount;
bool refreshReq;
uint8_t autoWhite;
uint8_t pins[OUTPUT_MAX_PINS] = {255, 255, 255, 255, 255};
uint8_t pins[5] = {255, 255, 255, 255, 255};
uint16_t frequency;
uint8_t milliAmpsPerLed;
uint16_t milliAmpsMax;

View File

@@ -244,61 +244,53 @@
typedef NeoEsp32I2s1Tm1914Method X1Tm1914Method;
#endif
// RMT driver selection
#if !defined(WLED_USE_SHARED_RMT) && !defined(__riscv)
#include <NeoEsp32RmtHIMethod.h>
#define NeoEsp32RmtMethod(x) NeoEsp32RmtHIN ## x ## Method
#else
#define NeoEsp32RmtMethod(x) NeoEsp32RmtN ## x ## Method
#endif
//RGB
#define B_32_RN_NEO_3 NeoPixelBus<NeoGrbFeature, NeoEsp32RmtMethod(Ws2812x)> // ESP32, S2, S3, C3
#define B_32_RN_NEO_3 NeoPixelBus<NeoGrbFeature, NeoEsp32RmtNWs2812xMethod> // ESP32, S2, S3, C3
//#define B_32_IN_NEO_3 NeoPixelBus<NeoGrbFeature, NeoEsp32I2sNWs2812xMethod> // ESP32 (dynamic I2S selection)
#define B_32_I2_NEO_3 NeoPixelBus<NeoGrbFeature, X1Ws2812xMethod> // ESP32, S2, S3 (automatic I2S selection, see typedef above)
#define B_32_IP_NEO_3 NeoPixelBus<NeoGrbFeature, X8Ws2812xMethod> // parallel I2S (ESP32, S2, S3)
//RGBW
#define B_32_RN_NEO_4 NeoPixelBus<NeoGrbwFeature, NeoEsp32RmtMethod(Sk6812)>
#define B_32_RN_NEO_4 NeoPixelBus<NeoGrbwFeature, NeoEsp32RmtNSk6812Method>
#define B_32_I2_NEO_4 NeoPixelBus<NeoGrbwFeature, X1Sk6812Method>
#define B_32_IP_NEO_4 NeoPixelBus<NeoGrbwFeature, X8Sk6812Method> // parallel I2S
//400Kbps
#define B_32_RN_400_3 NeoPixelBus<NeoGrbFeature, NeoEsp32RmtMethod(400Kbps)>
#define B_32_RN_400_3 NeoPixelBus<NeoGrbFeature, NeoEsp32RmtN400KbpsMethod>
#define B_32_I2_400_3 NeoPixelBus<NeoGrbFeature, X1400KbpsMethod>
#define B_32_IP_400_3 NeoPixelBus<NeoGrbFeature, X8400KbpsMethod> // parallel I2S
//TM1814 (RGBW)
#define B_32_RN_TM1_4 NeoPixelBus<NeoWrgbTm1814Feature, NeoEsp32RmtMethod(Tm1814)>
#define B_32_RN_TM1_4 NeoPixelBus<NeoWrgbTm1814Feature, NeoEsp32RmtNTm1814Method>
#define B_32_I2_TM1_4 NeoPixelBus<NeoWrgbTm1814Feature, X1Tm1814Method>
#define B_32_IP_TM1_4 NeoPixelBus<NeoWrgbTm1814Feature, X8Tm1814Method> // parallel I2S
//TM1829 (RGB)
#define B_32_RN_TM2_3 NeoPixelBus<NeoBrgFeature, NeoEsp32RmtMethod(Tm1829)>
#define B_32_RN_TM2_3 NeoPixelBus<NeoBrgFeature, NeoEsp32RmtNTm1829Method>
#define B_32_I2_TM2_3 NeoPixelBus<NeoBrgFeature, X1Tm1829Method>
#define B_32_IP_TM2_3 NeoPixelBus<NeoBrgFeature, X8Tm1829Method> // parallel I2S
//UCS8903
#define B_32_RN_UCS_3 NeoPixelBus<NeoRgbUcs8903Feature, NeoEsp32RmtMethod(Ws2812x)>
#define B_32_RN_UCS_3 NeoPixelBus<NeoRgbUcs8903Feature, NeoEsp32RmtNWs2812xMethod>
#define B_32_I2_UCS_3 NeoPixelBus<NeoRgbUcs8903Feature, X1800KbpsMethod>
#define B_32_IP_UCS_3 NeoPixelBus<NeoRgbUcs8903Feature, X8800KbpsMethod> // parallel I2S
//UCS8904
#define B_32_RN_UCS_4 NeoPixelBus<NeoRgbwUcs8904Feature, NeoEsp32RmtMethod(Ws2812x)>
#define B_32_RN_UCS_4 NeoPixelBus<NeoRgbwUcs8904Feature, NeoEsp32RmtNWs2812xMethod>
#define B_32_I2_UCS_4 NeoPixelBus<NeoRgbwUcs8904Feature, X1800KbpsMethod>
#define B_32_IP_UCS_4 NeoPixelBus<NeoRgbwUcs8904Feature, X8800KbpsMethod>// parallel I2S
//APA106
#define B_32_RN_APA106_3 NeoPixelBus<NeoGrbFeature, NeoEsp32RmtMethod(Apa106)>
#define B_32_RN_APA106_3 NeoPixelBus<NeoGrbFeature, NeoEsp32RmtNApa106Method>
#define B_32_I2_APA106_3 NeoPixelBus<NeoGrbFeature, X1Apa106Method>
#define B_32_IP_APA106_3 NeoPixelBus<NeoGrbFeature, X8Apa106Method> // parallel I2S
//FW1906 GRBCW
#define B_32_RN_FW6_5 NeoPixelBus<NeoGrbcwxFeature, NeoEsp32RmtMethod(Ws2812x)>
#define B_32_RN_FW6_5 NeoPixelBus<NeoGrbcwxFeature, NeoEsp32RmtNWs2812xMethod>
#define B_32_I2_FW6_5 NeoPixelBus<NeoGrbcwxFeature, X1800KbpsMethod>
#define B_32_IP_FW6_5 NeoPixelBus<NeoGrbcwxFeature, X8800KbpsMethod> // parallel I2S
//WS2805 RGBWC
#define B_32_RN_2805_5 NeoPixelBus<NeoGrbwwFeature, NeoEsp32RmtMethod(Ws2805)>
#define B_32_RN_2805_5 NeoPixelBus<NeoGrbwwFeature, NeoEsp32RmtNWs2805Method>
#define B_32_I2_2805_5 NeoPixelBus<NeoGrbwwFeature, X1Ws2805Method>
#define B_32_IP_2805_5 NeoPixelBus<NeoGrbwwFeature, X8Ws2805Method> // parallel I2S
//TM1914 (RGB)
#define B_32_RN_TM1914_3 NeoPixelBus<NeoGrbTm1914Feature, NeoEsp32RmtMethod(Tm1914)>
#define B_32_RN_TM1914_3 NeoPixelBus<NeoGrbTm1914Feature, NeoEsp32RmtNTm1914Method>
#define B_32_I2_TM1914_3 NeoPixelBus<NeoGrbTm1914Feature, X1Tm1914Method>
#define B_32_IP_TM1914_3 NeoPixelBus<NeoGrbTm1914Feature, X8Tm1914Method> // parallel I2S
//Sm16825 (RGBWC)
#define B_32_RN_SM16825_5 NeoPixelBus<NeoRgbcwSm16825eFeature, NeoEsp32RmtMethod(Ws2812x)>
#define B_32_RN_SM16825_5 NeoPixelBus<NeoRgbcwSm16825eFeature, NeoEsp32RmtNWs2812xMethod>
#define B_32_I2_SM16825_5 NeoPixelBus<NeoRgbcwSm16825eFeature, X1Ws2812xMethod>
#define B_32_IP_SM16825_5 NeoPixelBus<NeoRgbcwSm16825eFeature, X8Ws2812xMethod> // parallel I2S
#endif
@@ -1121,54 +1113,54 @@ class PolyBus {
switch (busType) {
case I_NONE: break;
#ifdef ESP8266
case I_8266_U0_NEO_3: size = (static_cast<B_8266_U0_NEO_3*>(busPtr))->PixelsSize(); break;
case I_8266_U1_NEO_3: size = (static_cast<B_8266_U1_NEO_3*>(busPtr))->PixelsSize(); break;
case I_8266_U0_NEO_3: size = (static_cast<B_8266_U0_NEO_3*>(busPtr))->PixelsSize()*2; break;
case I_8266_U1_NEO_3: size = (static_cast<B_8266_U1_NEO_3*>(busPtr))->PixelsSize()*2; break;
case I_8266_DM_NEO_3: size = (static_cast<B_8266_DM_NEO_3*>(busPtr))->PixelsSize()*5; break;
case I_8266_BB_NEO_3: size = (static_cast<B_8266_BB_NEO_3*>(busPtr))->PixelsSize(); break;
case I_8266_U0_NEO_4: size = (static_cast<B_8266_U0_NEO_4*>(busPtr))->PixelsSize(); break;
case I_8266_U1_NEO_4: size = (static_cast<B_8266_U1_NEO_4*>(busPtr))->PixelsSize(); break;
case I_8266_BB_NEO_3: size = (static_cast<B_8266_BB_NEO_3*>(busPtr))->PixelsSize()*2; break;
case I_8266_U0_NEO_4: size = (static_cast<B_8266_U0_NEO_4*>(busPtr))->PixelsSize()*2; break;
case I_8266_U1_NEO_4: size = (static_cast<B_8266_U1_NEO_4*>(busPtr))->PixelsSize()*2; break;
case I_8266_DM_NEO_4: size = (static_cast<B_8266_DM_NEO_4*>(busPtr))->PixelsSize()*5; break;
case I_8266_BB_NEO_4: size = (static_cast<B_8266_BB_NEO_4*>(busPtr))->PixelsSize(); break;
case I_8266_U0_400_3: size = (static_cast<B_8266_U0_400_3*>(busPtr))->PixelsSize(); break;
case I_8266_U1_400_3: size = (static_cast<B_8266_U1_400_3*>(busPtr))->PixelsSize(); break;
case I_8266_BB_NEO_4: size = (static_cast<B_8266_BB_NEO_4*>(busPtr))->PixelsSize()*2; break;
case I_8266_U0_400_3: size = (static_cast<B_8266_U0_400_3*>(busPtr))->PixelsSize()*2; break;
case I_8266_U1_400_3: size = (static_cast<B_8266_U1_400_3*>(busPtr))->PixelsSize()*2; break;
case I_8266_DM_400_3: size = (static_cast<B_8266_DM_400_3*>(busPtr))->PixelsSize()*5; break;
case I_8266_BB_400_3: size = (static_cast<B_8266_BB_400_3*>(busPtr))->PixelsSize(); break;
case I_8266_U0_TM1_4: size = (static_cast<B_8266_U0_TM1_4*>(busPtr))->PixelsSize(); break;
case I_8266_U1_TM1_4: size = (static_cast<B_8266_U1_TM1_4*>(busPtr))->PixelsSize(); break;
case I_8266_BB_400_3: size = (static_cast<B_8266_BB_400_3*>(busPtr))->PixelsSize()*2; break;
case I_8266_U0_TM1_4: size = (static_cast<B_8266_U0_TM1_4*>(busPtr))->PixelsSize()*2; break;
case I_8266_U1_TM1_4: size = (static_cast<B_8266_U1_TM1_4*>(busPtr))->PixelsSize()*2; break;
case I_8266_DM_TM1_4: size = (static_cast<B_8266_DM_TM1_4*>(busPtr))->PixelsSize()*5; break;
case I_8266_BB_TM1_4: size = (static_cast<B_8266_BB_TM1_4*>(busPtr))->PixelsSize(); break;
case I_8266_U0_TM2_3: size = (static_cast<B_8266_U0_TM2_3*>(busPtr))->PixelsSize(); break;
case I_8266_U1_TM2_3: size = (static_cast<B_8266_U1_TM2_3*>(busPtr))->PixelsSize(); break;
case I_8266_BB_TM1_4: size = (static_cast<B_8266_BB_TM1_4*>(busPtr))->PixelsSize()*2; break;
case I_8266_U0_TM2_3: size = (static_cast<B_8266_U0_TM2_3*>(busPtr))->PixelsSize()*2; break;
case I_8266_U1_TM2_3: size = (static_cast<B_8266_U1_TM2_3*>(busPtr))->PixelsSize()*2; break;
case I_8266_DM_TM2_3: size = (static_cast<B_8266_DM_TM2_3*>(busPtr))->PixelsSize()*5; break;
case I_8266_BB_TM2_3: size = (static_cast<B_8266_BB_TM2_3*>(busPtr))->PixelsSize(); break;
case I_8266_U0_UCS_3: size = (static_cast<B_8266_U0_UCS_3*>(busPtr))->PixelsSize(); break;
case I_8266_U1_UCS_3: size = (static_cast<B_8266_U1_UCS_3*>(busPtr))->PixelsSize(); break;
case I_8266_BB_TM2_3: size = (static_cast<B_8266_BB_TM2_3*>(busPtr))->PixelsSize()*2; break;
case I_8266_U0_UCS_3: size = (static_cast<B_8266_U0_UCS_3*>(busPtr))->PixelsSize()*2; break;
case I_8266_U1_UCS_3: size = (static_cast<B_8266_U1_UCS_3*>(busPtr))->PixelsSize()*2; break;
case I_8266_DM_UCS_3: size = (static_cast<B_8266_DM_UCS_3*>(busPtr))->PixelsSize()*5; break;
case I_8266_BB_UCS_3: size = (static_cast<B_8266_BB_UCS_3*>(busPtr))->PixelsSize(); break;
case I_8266_U0_UCS_4: size = (static_cast<B_8266_U0_UCS_4*>(busPtr))->PixelsSize(); break;
case I_8266_U1_UCS_4: size = (static_cast<B_8266_U1_UCS_4*>(busPtr))->PixelsSize(); break;
case I_8266_BB_UCS_3: size = (static_cast<B_8266_BB_UCS_3*>(busPtr))->PixelsSize()*2; break;
case I_8266_U0_UCS_4: size = (static_cast<B_8266_U0_UCS_4*>(busPtr))->PixelsSize()*2; break;
case I_8266_U1_UCS_4: size = (static_cast<B_8266_U1_UCS_4*>(busPtr))->PixelsSize()*2; break;
case I_8266_DM_UCS_4: size = (static_cast<B_8266_DM_UCS_4*>(busPtr))->PixelsSize()*5; break;
case I_8266_BB_UCS_4: size = (static_cast<B_8266_BB_UCS_4*>(busPtr))->PixelsSize(); break;
case I_8266_U0_APA106_3: size = (static_cast<B_8266_U0_APA106_3*>(busPtr))->PixelsSize(); break;
case I_8266_U1_APA106_3: size = (static_cast<B_8266_U1_APA106_3*>(busPtr))->PixelsSize(); break;
case I_8266_BB_UCS_4: size = (static_cast<B_8266_BB_UCS_4*>(busPtr))->PixelsSize()*2; break;
case I_8266_U0_APA106_3: size = (static_cast<B_8266_U0_APA106_3*>(busPtr))->PixelsSize()*2; break;
case I_8266_U1_APA106_3: size = (static_cast<B_8266_U1_APA106_3*>(busPtr))->PixelsSize()*2; break;
case I_8266_DM_APA106_3: size = (static_cast<B_8266_DM_APA106_3*>(busPtr))->PixelsSize()*5; break;
case I_8266_BB_APA106_3: size = (static_cast<B_8266_BB_APA106_3*>(busPtr))->PixelsSize(); break;
case I_8266_U0_FW6_5: size = (static_cast<B_8266_U0_FW6_5*>(busPtr))->PixelsSize(); break;
case I_8266_U1_FW6_5: size = (static_cast<B_8266_U1_FW6_5*>(busPtr))->PixelsSize(); break;
case I_8266_BB_APA106_3: size = (static_cast<B_8266_BB_APA106_3*>(busPtr))->PixelsSize()*2; break;
case I_8266_U0_FW6_5: size = (static_cast<B_8266_U0_FW6_5*>(busPtr))->PixelsSize()*2; break;
case I_8266_U1_FW6_5: size = (static_cast<B_8266_U1_FW6_5*>(busPtr))->PixelsSize()*2; break;
case I_8266_DM_FW6_5: size = (static_cast<B_8266_DM_FW6_5*>(busPtr))->PixelsSize()*5; break;
case I_8266_BB_FW6_5: size = (static_cast<B_8266_BB_FW6_5*>(busPtr))->PixelsSize(); break;
case I_8266_U0_2805_5: size = (static_cast<B_8266_U0_2805_5*>(busPtr))->PixelsSize(); break;
case I_8266_U1_2805_5: size = (static_cast<B_8266_U1_2805_5*>(busPtr))->PixelsSize(); break;
case I_8266_BB_FW6_5: size = (static_cast<B_8266_BB_FW6_5*>(busPtr))->PixelsSize()*2; break;
case I_8266_U0_2805_5: size = (static_cast<B_8266_U0_2805_5*>(busPtr))->PixelsSize()*2; break;
case I_8266_U1_2805_5: size = (static_cast<B_8266_U1_2805_5*>(busPtr))->PixelsSize()*2; break;
case I_8266_DM_2805_5: size = (static_cast<B_8266_DM_2805_5*>(busPtr))->PixelsSize()*5; break;
case I_8266_BB_2805_5: size = (static_cast<B_8266_BB_2805_5*>(busPtr))->PixelsSize(); break;
case I_8266_U0_TM1914_3: size = (static_cast<B_8266_U0_TM1914_3*>(busPtr))->PixelsSize(); break;
case I_8266_U1_TM1914_3: size = (static_cast<B_8266_U1_TM1914_3*>(busPtr))->PixelsSize(); break;
case I_8266_BB_2805_5: size = (static_cast<B_8266_BB_2805_5*>(busPtr))->PixelsSize()*2; break;
case I_8266_U0_TM1914_3: size = (static_cast<B_8266_U0_TM1914_3*>(busPtr))->PixelsSize()*2; break;
case I_8266_U1_TM1914_3: size = (static_cast<B_8266_U1_TM1914_3*>(busPtr))->PixelsSize()*2; break;
case I_8266_DM_TM1914_3: size = (static_cast<B_8266_DM_TM1914_3*>(busPtr))->PixelsSize()*5; break;
case I_8266_BB_TM1914_3: size = (static_cast<B_8266_BB_TM1914_3*>(busPtr))->PixelsSize(); break;
case I_8266_U0_SM16825_5: size = (static_cast<B_8266_U0_SM16825_5*>(busPtr))->PixelsSize(); break;
case I_8266_U1_SM16825_5: size = (static_cast<B_8266_U1_SM16825_5*>(busPtr))->PixelsSize(); break;
case I_8266_BB_TM1914_3: size = (static_cast<B_8266_BB_TM1914_3*>(busPtr))->PixelsSize()*2; break;
case I_8266_U0_SM16825_5: size = (static_cast<B_8266_U0_SM16825_5*>(busPtr))->PixelsSize()*2; break;
case I_8266_U1_SM16825_5: size = (static_cast<B_8266_U1_SM16825_5*>(busPtr))->PixelsSize()*2; break;
case I_8266_DM_SM16825_5: size = (static_cast<B_8266_DM_SM16825_5*>(busPtr))->PixelsSize()*5; break;
case I_8266_BB_SM16825_5: size = (static_cast<B_8266_BB_SM16825_5*>(busPtr))->PixelsSize(); break;
case I_8266_BB_SM16825_5: size = (static_cast<B_8266_BB_SM16825_5*>(busPtr))->PixelsSize()*2; break;
#endif
#ifdef ARDUINO_ARCH_ESP32
// RMT buses (front + back + small system managed RMT)
@@ -1220,65 +1212,68 @@ class PolyBus {
case I_NONE: size = 0; break;
#ifdef ESP8266
// UART methods have front + back buffers + small UART
case I_8266_U0_NEO_4 : // fallthrough
case I_8266_U1_NEO_4 : // fallthrough
case I_8266_BB_NEO_4 : // fallthrough
case I_8266_U0_TM1_4 : // fallthrough
case I_8266_U1_TM1_4 : // fallthrough
case I_8266_BB_TM1_4 : size = (size + count); break; // 4 channels
case I_8266_U0_UCS_3 : // fallthrough
case I_8266_U1_UCS_3 : // fallthrough
case I_8266_BB_UCS_3 : size *= 2; break; // 16 bit
case I_8266_U0_UCS_4 : // fallthrough
case I_8266_U1_UCS_4 : // fallthrough
case I_8266_BB_UCS_4 : size = (size + count)*2; break; // 16 bit 4 channels
case I_8266_U0_FW6_5 : // fallthrough
case I_8266_U1_FW6_5 : // fallthrough
case I_8266_BB_FW6_5 : // fallthrough
case I_8266_U0_2805_5 : // fallthrough
case I_8266_U1_2805_5 : // fallthrough
case I_8266_BB_2805_5 : size = (size + 2*count); break; // 5 channels
case I_8266_U0_SM16825_5: // fallthrough
case I_8266_U1_SM16825_5: // fallthrough
case I_8266_BB_SM16825_5: size = (size + 2*count)*2; break; // 16 bit 5 channels
// DMA methods have front + DMA buffer = ((1+(3+1)) * channels; exact value is a bit of mistery - needs a dig into NPB)
case I_8266_DM_NEO_3 : // fallthrough
case I_8266_DM_400_3 : // fallthrough
case I_8266_DM_TM2_3 : // fallthrough
case I_8266_DM_APA106_3 : // fallthrough
case I_8266_DM_TM1914_3 : size *= 5; break;
case I_8266_DM_NEO_4 : // fallthrough
case I_8266_DM_TM1_4 : size = (size + count)*5; break;
case I_8266_DM_UCS_3 : size *= 2*5; break;
case I_8266_DM_UCS_4 : size = (size + count)*2*5; break;
case I_8266_DM_FW6_5 : // fallthrough
case I_8266_DM_2805_5 : size = (size + 2*count)*5; break;
case I_8266_U0_NEO_4: size = (size + count)*2; break; // 4 channels
case I_8266_U1_NEO_4: size = (size + count)*2; break; // 4 channels
case I_8266_BB_NEO_4: size = (size + count)*2; break; // 4 channels
case I_8266_U0_TM1_4: size = (size + count)*2; break; // 4 channels
case I_8266_U1_TM1_4: size = (size + count)*2; break; // 4 channels
case I_8266_BB_TM1_4: size = (size + count)*2; break; // 4 channels
case I_8266_U0_UCS_3: size *= 4; break; // 16 bit
case I_8266_U1_UCS_3: size *= 4; break; // 16 bit
case I_8266_BB_UCS_3: size *= 4; break; // 16 bit
case I_8266_U0_UCS_4: size = (size + count)*2*2; break; // 16 bit 4 channels
case I_8266_U1_UCS_4: size = (size + count)*2*2; break; // 16 bit 4 channels
case I_8266_BB_UCS_4: size = (size + count)*2*2; break; // 16 bit 4 channels
case I_8266_U0_FW6_5: size = (size + 2*count)*2; break; // 5 channels
case I_8266_U1_FW6_5: size = (size + 2*count)*2; break; // 5channels
case I_8266_BB_FW6_5: size = (size + 2*count)*2; break; // 5 channels
case I_8266_U0_2805_5: size = (size + 2*count)*2; break; // 5 channels
case I_8266_U1_2805_5: size = (size + 2*count)*2; break; // 5 channels
case I_8266_BB_2805_5: size = (size + 2*count)*2; break; // 5 channels
case I_8266_U0_SM16825_5: size = (size + 2*count)*2*2; break; // 16 bit 5 channels
case I_8266_U1_SM16825_5: size = (size + 2*count)*2*2; break; // 16 bit 5 channels
case I_8266_BB_SM16825_5: size = (size + 2*count)*2*2; break; // 16 bit 5 channels
// DMA methods have front + DMA buffer = ((1+(3+1)) * channels)
case I_8266_DM_NEO_3: size *= 5; break;
case I_8266_DM_NEO_4: size = (size + count)*5; break;
case I_8266_DM_400_3: size *= 5; break;
case I_8266_DM_TM1_4: size = (size + count)*5; break;
case I_8266_DM_TM2_3: size *= 5; break;
case I_8266_DM_UCS_3: size *= 2*5; break;
case I_8266_DM_UCS_4: size = (size + count)*2*5; break;
case I_8266_DM_APA106_3: size *= 5; break;
case I_8266_DM_FW6_5: size = (size + 2*count)*5; break;
case I_8266_DM_2805_5: size = (size + 2*count)*5; break;
case I_8266_DM_TM1914_3: size *= 5; break;
case I_8266_DM_SM16825_5: size = (size + 2*count)*2*5; break;
#else
// RMT buses (1x front and 1x back buffer, does not include small RMT buffer)
case I_32_RN_NEO_4 : // fallthrough
case I_32_RN_TM1_4 : size = (size + count)*2; break; // 4 channels
case I_32_RN_UCS_3 : size *= 2*2; break; // 16bit
case I_32_RN_UCS_4 : size = (size + count)*2*2; break; // 16bit, 4 channels
case I_32_RN_FW6_5 : // fallthrough
case I_32_RN_2805_5 : size = (size + 2*count)*2; break; // 5 channels
case I_32_RN_SM16825_5: size = (size + 2*count)*2*2; break; // 16bit, 5 channels
// I2S1 bus or paralell I2S1 buses (1x front, does not include DMA buffer which is front*cadence, a bit(?) more for LCD)
#ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I2_NEO_3 : // fallthrough
case I_32_I2_400_3 : // fallthrough
case I_32_I2_TM2_3 : // fallthrough
case I_32_I2_APA106_3 : break; // do nothing, I2S uses single buffer + DMA buffer
case I_32_I2_NEO_4 : // fallthrough
case I_32_I2_TM1_4 : size = (size + count); break; // 4 channels
case I_32_I2_UCS_3 : size *= 2; break; // 16 bit
case I_32_I2_UCS_4 : size = (size + count)*2; break; // 16 bit, 4 channels
case I_32_I2_FW6_5 : // fallthrough
case I_32_I2_2805_5 : size = (size + 2*count); break; // 5 channels
case I_32_I2_SM16825_5: size = (size + 2*count)*2; break; // 16 bit, 5 channels
#endif
default : size *= 2; break; // everything else uses 2 buffers
#endif
#ifdef ARDUINO_ARCH_ESP32
// RMT buses (1x front and 1x back buffer)
case I_32_RN_NEO_4: size = (size + count)*2; break;
case I_32_RN_TM1_4: size = (size + count)*2; break;
case I_32_RN_UCS_3: size *= 2*2; break;
case I_32_RN_UCS_4: size = (size + count)*2*2; break;
case I_32_RN_FW6_5: size = (size + 2*count)*2; break;
case I_32_RN_2805_5: size = (size + 2*count)*2; break;
case I_32_RN_SM16825_5: size = (size + 2*count)*2*2; break;
// I2S1 bus or paralell buses (individual 1x front and 1 DMA (3x or 4x pixel count) or common back DMA buffers)
#ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I2_NEO_3: size *= 4; break;
case I_32_I2_NEO_4: size = (size + count)*4; break;
case I_32_I2_400_3: size *= 4; break;
case I_32_I2_TM1_4: size = (size + count)*4; break;
case I_32_I2_TM2_3: size *= 4; break;
case I_32_I2_UCS_3: size *= 2*4; break;
case I_32_I2_UCS_4: size = (size + count)*2*4; break;
case I_32_I2_APA106_3: size *= 4; break;
case I_32_I2_FW6_5: size = (size + 2*count)*4; break;
case I_32_I2_2805_5: size = (size + 2*count)*4; break;
case I_32_I2_TM1914_3: size *= 4; break;
case I_32_I2_SM16825_5: size = (size + 2*count)*2*4; break;
#endif
#endif
// everything else uses 2 buffers
default: size *= 2; break;
}
return size;
}

View File

@@ -201,13 +201,13 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
}
#endif
DEBUG_PRINTF_P(PSTR("Heap before buses: %d\n"), getFreeHeapSize());
DEBUG_PRINTF_P(PSTR("Heap before buses: %d\n"), ESP.getFreeHeap());
JsonArray ins = hw_led["ins"];
if (!ins.isNull()) {
int s = 0; // bus iterator
for (JsonObject elm : ins) {
if (s >= WLED_MAX_BUSSES) break; // only counts physical buses
uint8_t pins[OUTPUT_MAX_PINS] = {255, 255, 255, 255, 255};
uint8_t pins[5] = {255, 255, 255, 255, 255};
JsonArray pinArr = elm["pin"];
if (pinArr.size() == 0) continue;
//pins[0] = pinArr[0];
@@ -256,7 +256,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
static_assert(validatePinsAndTypes(defDataTypes, defNumTypes, defNumPins),
"The default pin list defined in DATA_PINS does not match the pin requirements for the default buses defined in LED_TYPES");
unsigned mem = 0;
unsigned pinsIndex = 0;
unsigned digitalCount = 0;
for (unsigned i = 0; i < WLED_MAX_BUSSES; i++) {
uint8_t defPin[OUTPUT_MAX_PINS];
// if we have less types than requested outputs and they do not align, use last known type to set current type
@@ -319,9 +321,16 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
unsigned start = 0;
// analog always has length 1
if (Bus::isPWM(dataType) || Bus::isOnOff(dataType)) count = 1;
busConfigs.emplace_back(dataType, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY, 0);
BusConfig defCfg = BusConfig(dataType, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY, 0);
mem += defCfg.memUsage(Bus::isDigital(dataType) && !Bus::is2Pin(dataType) ? digitalCount++ : 0);
if (mem > MAX_LED_MEMORY) {
DEBUG_PRINTF_P(PSTR("Out of LED memory! Bus %d (%d) #%u not created."), (int)dataType, (int)count, digitalCount);
break;
}
busConfigs.push_back(defCfg); // use push_back for simplification as we needed defCfg to calculate memory usage
doInitBusses = true; // finalization done in beginStrip()
}
DEBUG_PRINTF_P(PSTR("LED buffer size: %uB/%uB\n"), mem, BusManager::memUsage());
}
if (hw_led["rev"] && BusManager::getNumBusses()) BusManager::getBus(0)->setReversed(true); //set 0.11 global reversed setting for first bus
@@ -777,10 +786,6 @@ bool verifyConfig() {
return validateJsonFile(s_cfg_json);
}
bool configBackupExists() {
return checkBackupExists(s_cfg_json);
}
// rename config file and reboot
// if the cfg file doesn't exist, such as after a reset, do nothing
void resetConfig() {

View File

@@ -8,7 +8,7 @@
* 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 WLED_O2_ATTR IRAM_ATTR color_blend(uint32_t color1, uint32_t color2, uint8_t blend) {
uint32_t IRAM_ATTR color_blend(uint32_t color1, uint32_t color2, uint8_t blend) {
// min / max blend checking is omitted: calls with 0 or 255 are rare, checking lowers overall performance
const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF; // mask for R and B channels or W and G if negated (poorman's SIMD; https://github.com/wled/WLED/pull/4568#discussion_r1986587221)
uint32_t rb1 = color1 & TWO_CHANNEL_MASK; // extract R & B channels from color1
@@ -25,37 +25,39 @@ uint32_t WLED_O2_ATTR IRAM_ATTR color_blend(uint32_t color1, uint32_t color2, ui
* original idea: https://github.com/wled-dev/WLED/pull/2465 by https://github.com/Proto-molecule
* speed optimisations by @dedehai
*/
uint32_t WLED_O2_ATTR color_add(uint32_t c1, uint32_t c2, bool preserveCR) //1212558 | 1212598 | 1212576 | 1212530
uint32_t color_add(uint32_t c1, uint32_t c2, bool preserveCR)
{
if (c1 == BLACK) return c2;
if (c2 == BLACK) return c1;
const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF; // mask for R and B channels or W and G if negated
uint32_t rb = ( c1 & TWO_CHANNEL_MASK) + ( c2 & TWO_CHANNEL_MASK); // mask and add two colors at once
uint32_t wg = ((c1>>8) & TWO_CHANNEL_MASK) + ((c2>>8) & TWO_CHANNEL_MASK);
uint32_t r = rb >> 16; // extract single color values
uint32_t b = rb & 0xFFFF;
uint32_t w = wg >> 16;
uint32_t g = wg & 0xFFFF;
if (preserveCR) { // preserve color ratios
uint32_t overflow = (rb | wg) & 0x01000100; // detect overflow by checking 9th bit
if (overflow) {
uint32_t r = rb >> 16; // extract single color values
uint32_t b = rb & 0xFFFF;
uint32_t w = wg >> 16;
uint32_t g = wg & 0xFFFF;
uint32_t max = std::max(r,g);
max = std::max(max,b);
max = std::max(max,w);
uint32_t max = std::max(r,g); // check for overflow note
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) {
const 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) & TWO_CHANNEL_MASK;
wg = (wg * scale) & ~TWO_CHANNEL_MASK;
} else wg <<= 8; //shift white and green back to correct position
return rb | wg;
} else {
// branchless per-channel saturation to 255 (extract 9th bit, subtract 1 if it is set, mask with 0xFF, input is 0xFF+0xFF=0x1EF max)
// example with overflow: input: 0x01EF01EF -> (0x0100100 - 0x00010001) = 0x00FF00FF -> input|0x00FF00FF = 0x00FF00FF (saturate)
// example without overflow: input: 0x007F007F -> (0x00000000 - 0x00000000) = 0x00000000 -> input|0x00000000 = input (no change)
rb |= ((rb & 0x01000100) - ((rb >> 8) & 0x00010001)) & 0x00FF00FF;
wg |= ((wg & 0x01000100) - ((wg >> 8) & 0x00010001)) & 0x00FF00FF;
wg <<= 8; // restore WG position
r = r > 255 ? 255 : r;
g = g > 255 ? 255 : g;
b = b > 255 ? 255 : b;
w = w > 255 ? 255 : w;
return RGBW32(r,g,b,w);
}
return rb | wg;
}
/*
@@ -72,10 +74,11 @@ uint32_t IRAM_ATTR color_fade(uint32_t c1, uint8_t amount, bool video) {
// video scaling: make sure colors do not dim to zero if they started non-zero unless they distort the hue
uint8_t r = byte(c1>>16), g = byte(c1>>8), b = byte(c1), w = byte(c1>>24); // extract r, g, b, w channels
uint8_t maxc = (r > g) ? ((r > b) ? r : b) : ((g > b) ? g : b); // determine dominant channel for hue preservation
addRemains = r && (r<<5) > maxc ? 0x00010000 : 0; // note: setting color preservation threshold too high results in flickering and
addRemains |= g && (g<<5) > maxc ? 0x00000100 : 0; // jumping colors in low brightness gradients. Multiplying the color preserves
addRemains |= b && (b<<5) > maxc ? 0x00000001 : 0; // better accuracy than dividing the maxc. Shifting by 5 is a good compromise
addRemains |= w ? 0x01000000 : 0; // i.e. remove color channel if <13% of max
uint8_t quarterMax = maxc >> 2; // note: using half of max results in color artefacts
addRemains = r && r > quarterMax ? 0x00010000 : 0;
addRemains |= g && g > quarterMax ? 0x00000100 : 0;
addRemains |= b && b > quarterMax ? 0x00000001 : 0;
addRemains |= w ? 0x01000000 : 0;
}
const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF;
uint32_t rb = (((c1 & TWO_CHANNEL_MASK) * amount) >> 8) & TWO_CHANNEL_MASK; // scale red and blue
@@ -89,15 +92,15 @@ uint32_t IRAM_ATTR color_fade(uint32_t c1, uint8_t amount, bool video) {
note: inputs are 32bit to speed up the function, useful input value ranges are 0-255
*/
uint32_t adjust_color(uint32_t rgb, uint32_t hueShift, uint32_t lighten, uint32_t brighten) {
if (rgb == 0 || hueShift + lighten + brighten == 0) return rgb; // black or no change
CHSV32 hsv;
rgb2hsv(rgb, hsv); //convert to HSV
hsv.h += (hueShift << 8); // shift hue (hue is 16 bits)
hsv.s = max((int32_t)0, (int32_t)hsv.s - (int32_t)lighten); // desaturate
hsv.v = min((uint32_t)255, (uint32_t)hsv.v + brighten); // increase brightness
uint32_t rgb_adjusted;
hsv2rgb(hsv, rgb_adjusted); // convert back to RGB TODO: make this into 16 bit conversion
return rgb_adjusted;
if (rgb == 0 | hueShift + lighten + brighten == 0) return rgb; // black or no change
CHSV32 hsv;
rgb2hsv(rgb, hsv); //convert to HSV
hsv.h += (hueShift << 8); // shift hue (hue is 16 bits)
hsv.s = max((int32_t)0, (int32_t)hsv.s - (int32_t)lighten); // desaturate
hsv.v = min((uint32_t)255, (uint32_t)hsv.v + brighten); // increase brightness
uint32_t rgb_adjusted;
hsv2rgb(hsv, rgb_adjusted); // convert back to RGB TODO: make this into 16 bit conversion
return rgb_adjusted;
}
// 1:1 replacement of fastled function optimized for ESP, slightly faster, more accurate and uses less flash (~ -200bytes)
@@ -249,7 +252,7 @@ void loadCustomPalettes() {
byte tcp[72]; //support gradient palettes with up to 18 entries
CRGBPalette16 targetPalette;
customPalettes.clear(); // start fresh
for (int index = 0; index < WLED_MAX_CUSTOM_PALETTES; index++) {
for (int index = 0; index<10; index++) {
char fileName[32];
sprintf_P(fileName, PSTR("/palette%d.json"), index);
@@ -594,13 +597,13 @@ void NeoGammaWLEDMethod::calcGammaTable(float gamma)
gammaT_inv[0] = 0;
}
uint8_t NeoGammaWLEDMethod::Correct(uint8_t value)
uint8_t IRAM_ATTR_YN NeoGammaWLEDMethod::Correct(uint8_t value)
{
if (!gammaCorrectCol) return value;
return gammaT[value];
}
uint32_t NeoGammaWLEDMethod::inverseGamma32(uint32_t color)
uint32_t IRAM_ATTR_YN NeoGammaWLEDMethod::inverseGamma32(uint32_t color)
{
if (!gammaCorrectCol) return color;
uint8_t w = W(color);

View File

@@ -117,14 +117,13 @@ class NeoGammaWLEDMethod {
[[gnu::hot, gnu::pure]] uint32_t color_blend(uint32_t c1, uint32_t c2 , uint8_t blend);
inline uint32_t color_blend16(uint32_t c1, uint32_t c2, uint16_t b) { return color_blend(c1, c2, b >> 8); };
[[gnu::hot, gnu::pure]] uint32_t color_add(uint32_t, uint32_t, bool preserveCR = false);
[[gnu::hot, gnu::pure]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video = false);
[[gnu::hot, gnu::pure]] uint32_t adjust_color(uint32_t rgb, uint32_t hueShift, uint32_t lighten, uint32_t brighten);
[[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();
void loadCustomPalettes();
extern std::vector<CRGBPalette16> customPalettes;
inline size_t getPaletteCount() { return FIXED_PALETTE_COUNT + customPalettes.size(); }
inline size_t getPaletteCount() { return 13 + GRADIENT_PALETTE_COUNT + customPalettes.size(); }
inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); }
void hsv2rgb(const CHSV32& hsv, uint32_t& rgb);
void colorHStoRGB(uint16_t hue, byte sat, byte* rgb);
@@ -140,16 +139,6 @@ uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
uint16_t approximateKelvinFromRGB(uint32_t rgb);
void setRandomColor(byte* rgb);
// fast scaling function for colors, performs color*scale/256 for all four channels, speed over accuracy
// note: inlining uses less code than actual function calls
static inline uint32_t fast_color_scale(const uint32_t c, const uint8_t scale) {
uint32_t rb = (((c & 0x00FF00FF) * scale) >> 8) & 0x00FF00FF;
uint32_t wg = (((c>>8) & 0x00FF00FF) * scale) & ~0x00FF00FF;
return rb | wg;
}
[[gnu::hot, gnu::pure]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video = false);
// palettes
extern const TProgmemRGBPalette16* const fastledPalettes[];
extern const uint8_t* const gGradientPalettes[];
#endif

View File

@@ -6,15 +6,7 @@
* Readability defines and their associated numerical values + compile-time constants
*/
constexpr size_t FASTLED_PALETTE_COUNT = 7; // = sizeof(fastledPalettes) / sizeof(fastledPalettes[0]);
constexpr size_t GRADIENT_PALETTE_COUNT = 59; // = sizeof(gGradientPalettes) / sizeof(gGradientPalettes[0]);
constexpr size_t DYNAMIC_PALETTE_COUNT = 5; // 1-5 are dynamic palettes (1=random,2=primary,3=primary+secondary,4=primary+secondary+tertiary,5=primary+secondary(+tertiary if not black)
constexpr size_t FIXED_PALETTE_COUNT = DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_COUNT + GRADIENT_PALETTE_COUNT; // total number of fixed palettes
#ifndef ESP8266
#define WLED_MAX_CUSTOM_PALETTES (255 - FIXED_PALETTE_COUNT) // allow up to 255 total palettes, user is warned about stability issues when adding more than 10
#else
#define WLED_MAX_CUSTOM_PALETTES 10 // ESP8266: limit custom palettes to 10
#endif
#define GRADIENT_PALETTE_COUNT 59
// You can define custom product info from build flags.
// This is useful to allow API consumer to identify what type of WLED version
@@ -324,12 +316,6 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
#define TYPE_P9813 53
#define TYPE_LPD6803 54
#define TYPE_2PIN_MAX 63
#define TYPE_HUB75MATRIX_MIN 64
#define TYPE_HUB75MATRIX_HS 65
#define TYPE_HUB75MATRIX_QS 66
#define TYPE_HUB75MATRIX_MAX 71
//Network types (master broadcast) (80-95)
#define TYPE_VIRTUAL_MIN 80
#define TYPE_NET_DDP_RGB 80 //network DDP RGB bus (master broadcast bus)
@@ -560,21 +546,8 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
#endif
#endif
// minimum heap size required to process web requests: try to keep free heap above this value
#ifdef ESP8266
#define MIN_HEAP_SIZE (9*1024)
#else
#define MIN_HEAP_SIZE (15*1024) // WLED allocation functions (util.cpp) try to keep this much contiguous heap free for other tasks
#endif
// threshold for PSRAM use: if heap is running low, requests to allocate_buffer(prefer DRAM) above PSRAM_THRESHOLD may be put in PSRAM
// if heap is depleted, PSRAM will be used regardless of threshold
#if defined(CONFIG_IDF_TARGET_ESP32S3)
#define PSRAM_THRESHOLD (12*1024) // S3 has plenty of DRAM
#elif defined(CONFIG_IDF_TARGET_ESP32)
#define PSRAM_THRESHOLD (5*1024)
#else
#define PSRAM_THRESHOLD (2*1024) // S2 does not have a lot of RAM. C3 and ESP8266 do not support PSRAM: the value is not used
#endif
// minimum heap size required to process web requests
#define MIN_HEAP_SIZE 8192
// Web server limits
#ifdef ESP8266
@@ -682,6 +655,4 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
#define IRAM_ATTR_YN IRAM_ATTR
#endif
#define WLED_O2_ATTR __attribute__((optimize("O2")))
#endif

View File

@@ -116,62 +116,3 @@ function uploadFile(fileObj, name) {
fileObj.value = '';
return false;
}
// connect to WebSocket, use parent WS or open new
function connectWs(onOpen) {
try {
if (top.window.ws && top.window.ws.readyState === WebSocket.OPEN) {
if (onOpen) onOpen();
return top.window.ws;
}
} catch (e) {}
getLoc(); // ensure globals (loc, locip, locproto) are up to date
let url = loc ? getURL('/ws').replace("http","ws") : "ws://"+window.location.hostname+"/ws";
let ws = new WebSocket(url);
ws.binaryType = "arraybuffer";
if (onOpen) { ws.onopen = onOpen; }
try { top.window.ws = ws; } catch (e) {} // store in parent for reuse
return ws;
}
// send LED colors to ESP using WebSocket and DDP protocol (RGB)
// ws: WebSocket object
// start: start pixel index
// len: number of pixels to send
// colors: Uint8Array with RGB values (3*len bytes)
function sendDDP(ws, start, len, colors) {
if (!colors || colors.length < len * 3) return false; // not enough color data
let maxDDPpx = 472; // must fit into one WebSocket frame of 1428 bytes, DDP header is 10+1 bytes -> 472 RGB pixels
//let maxDDPpx = 172; // ESP8266: must fit into one WebSocket frame of 528 bytes -> 172 RGB pixels TODO: add support for ESP8266?
if (!ws || ws.readyState !== WebSocket.OPEN) return false;
// send in chunks of maxDDPpx
for (let i = 0; i < len; i += maxDDPpx) {
let cnt = Math.min(maxDDPpx, len - i);
let off = (start + i) * 3; // DDP pixel offset in bytes
let dLen = cnt * 3;
let cOff = i * 3; // offset in color buffer
let pkt = new Uint8Array(11 + dLen); // DDP header is 10 bytes, plus 1 byte for WLED websocket protocol indicator
pkt[0] = 0x02; // DDP protocol indicator for WLED websocket. Note: below DDP protocol bytes are offset by 1
pkt[1] = 0x40; // flags: 0x40 = no push, 0x41 = push (i.e. render), note: this is DDP protocol byte 0
pkt[2] = 0x00; // reserved
pkt[3] = 0x01; // 1 = RGB (currently only supported mode)
pkt[4] = 0x01; // destination id (not used but 0x01 is default output)
pkt[5] = (off >> 24) & 255; // DDP protocol 4-7 is offset
pkt[6] = (off >> 16) & 255;
pkt[7] = (off >> 8) & 255;
pkt[8] = off & 255;
pkt[9] = (dLen >> 8) & 255; // DDP protocol 8-9 is data length
pkt[10] = dLen & 255;
pkt.set(colors.subarray(cOff, cOff + dLen), 11);
if(i + cnt >= len) {
pkt[1] = 0x41; //if this is last packet, set the "push" flag to render the frame
}
try {
ws.send(pkt.buffer);
} catch (e) {
console.error(e);
return false;
}
}
return true;
}

View File

@@ -11,8 +11,7 @@
function gId(e) {return d.getElementById(e);}
function cE(e) {return d.createElement(e);}
</script>
<script src="common.js" type="text/javascript"></script>
<style>
body {
font-family: Arial, sans-serif;
@@ -22,39 +21,39 @@
margin: 0 10px;
line-height: 0.5;
}
#pCont {
#parent-container {
position: relative;
width: 100%;
height: 20px;
}
#bCont {
#bottomContainer {
position: absolute;
margin-top: 50px;
}
#gBox {
#gradient-box {
width: 100%;
height: 100%;
}
.cMark, .cPickMark {
.color-marker, .color-picker-marker {
position: absolute;
border-radius: 3px;
background-color: rgb(192, 192, 192);
border: 2px solid rgba(68, 68, 68, 0.5);
z-index: 2;
}
.cMark {
.color-marker {
height: 30px;
width: 7px;
top: 50%;
transform: translateY(-50%);
touch-action: none;
}
.cPickMark {
.color-picker-marker {
height: 7px;
width: 7px;
top: 150%;
}
.dMark {
.delete-marker {
position: absolute;
height: 5px;
width: 5px;
@@ -64,7 +63,7 @@
top: 220%;
z-index: 2;
}
.cPick {
.color-picker {
position: absolute;
height: 1px;
width: 1px;
@@ -74,20 +73,21 @@
border-color: #111;
background-color: #111;
}
.btnCls {
.buttonclass {
padding: 0;
margin: 0;
vertical-align: bottom;
background-color: #111;
}
#bCont span {
#bottomContainer span {
display: inline-flex;
align-items: center;
color: #fff;
color: #fff;
font-size: 12px;
vertical-align: middle;
}
#info {
display: "";
text-align: center;
color: #fff;
font-size: 12px;
@@ -104,76 +104,119 @@
width: 800px;
}
}
.pal {height: 20px;}
.pGrads {flex: 1; height: 20px; border-radius: 3px;}
.pMain {margin-top: 50px; width: 100%;}
.pTop {height: fit-content; text-align: center; color: #fff; font-size: 14px; line-height: 1;}
.pGradPar {display: flex; align-items: center; height: fit-content; margin-top: 10px; text-align: center; color: #fff; font-size: 12px; line-height: 1;}
.btnsDiv {display: inline-flex; margin-left: 5px; width: 50px;}
.sSpan, .eSpan {cursor: pointer;}
h1 {font-size: 1.6rem;}
.palette {
height: 20px;
}
.paletteGradients {
flex: 1;
height: 20px;
border-radius: 3px;
}
.palettesMain {
margin-top: 50px;
width: 100%;
}
.palTop {
height: fit-content;
text-align: center;
color: #fff;
font-size: 12px;
position: relative;
line-height: 1;
}
.palGradientParent {
display: flex;
align-items: center;
height: fit-content;
margin-top: 10px;
text-align: center;
color: #fff;
font-size: 12px;
position: relative;
line-height: 1;
}
.buttonsDiv {
display: inline-flex;
margin-left: 5px;
width: 50px;
}
.sendSpan, .editSpan{
cursor: pointer;
}
h1 {
font-size: 1.6rem;
}
</style>
</head>
<body>
<div id="wrap" class="wrap">
<div style="display: flex; justify-content: center;">
<h1 style="display: flex; align-items: center;">
<svg style="width: 36px; height: 36px; margin-right: 6px;" viewBox="0 0 32 32">
<rect style="fill: #03F" x="6" y="22" width="8" height="4"/>
<rect style="fill: #03F" x="14" y="14" width="4" height="8"/>
<rect style="fill: #03F" x="18" y="10" width="4" height="8"/>
<rect style="fill: #03F" x="22" y="6" width="8" height="4"/>
<svg style="width:36px;height:36px;margin-right:6px;" viewBox="0 0 32 32">
<rect style="fill:#003FFF" x="6" y="22" width="8" height="4"/>
<rect style="fill:#003FFF" x="14" y="14" width="4" height="8"/>
<rect style="fill:#003FFF" x="18" y="10" width="4" height="8"/>
<rect style="fill:#003FFF" x="22" y="6" width="8" height="4"/>
</svg>
<span id="head">WLED Palette Editor</span>
<span id="head">WLED Custom Palette Editor</span>
</h1>
</div>
<div id="pCont"><div id="gBox"></div></div>
<div id="parent-container">
<div id="gradient-box"></div>
</div>
<div style="display: flex; justify-content: center;">
<div id="pals" class="pMain">
<div id="distDiv" class="pTop"></div>
<div id="memWarn" class="pTop" style="display:none; color:#ff6600; margin-bottom:8px; font-size:16px;">
Warning: Adding many custom palettes might cause stability issues, create <a href="/settings/sec#backup" style="color:#ff9900">backups</a> before proceeding.</div>
<div id="pTop" class="pTop">Custom palettes</div>
<div id="palettes" class="palettesMain">
<div id="distDiv" class="palTop"></div>
<div id="palTop" class="palTop">
Currently in use custom palettes
</div>
</div>
</div>
<div style="display: flex; justify-content: center;">
<div id="info">Click gradient to add. Box = color. Red = delete. Arrow = upload. Pencil = edit.</div>
</div>
<div style="display: flex; justify-content: center;">
<div id="sPals" class="pMain">
<div id="spTop" class="pTop">Static palettes</div>
<div id="info">
Click on the gradient editor to add new color slider, then the colored box below the slider to change its color.
Click the red box below indicator (and confirm) to delete.
Once finished, click the arrow icon to upload into the desired slot.
To edit existing palette, click the pencil icon.
</div>
</div>
<div style="display: flex; justify-content: center;">
<div id="staticPalettes" class="palettesMain">
<div id="statpalTop" class="palTop">
Available static palettes
</div>
</div>
</div>
</body>
<script type="text/javascript">
// global vars
var gBox = gId('gBox'); // gradientBox
var cpalc = -1, cpalm = 10; // current palette count, max custom
var pxCol = {}; // pixel color map
var tCol = {}; // true color map
var rect = gBox.getBoundingClientRect(); // bounding rect of gBox
var gLen = rect.width; // gradientLength
var mOffs = Math.round((gLen / 256) / 2) - 5; // marker offset
var palArr = []; // paletteArray
var palNm = []; // paletteName
//global variables
var gradientBox = gId('gradient-box');
var cpalc = -1;
var pxCol = {};
var tCol = {};
var rect = gradientBox.getBoundingClientRect();
var gradientLength = rect.width;
var mOffs = Math.round((gradientLength / 256) / 2) - 5;
var paletteArray = []; //Holds 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 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', distrib);
distDiv.setAttribute('title', 'Distribute equally');
distDiv.addEventListener('click', distribute);
distDiv.setAttribute('title', 'Distribute colors equally');
distDiv.innerHTML = svgDist;
function recOf() {
rect = gBox.getBoundingClientRect();
gLen = rect.width;
mOffs = Math.round((gLen / 256) / 2) - 5;
rect = gradientBox.getBoundingClientRect();
gradientLength = rect.width;
mOffs = Math.round((gradientLength / 256) / 2) - 5;
}
//Initiation
@@ -181,220 +224,277 @@
window.addEventListener('load', chkW);
window.addEventListener('resize', chkW);
gBox.addEventListener("click", clikGrad);
gradientBox.addEventListener("click", clikOnGradient);
//Sets start and stop, mandatory
addC(0);
addC(255);
updGrad(); // updateGradient at startup
updateGradient(); //Sets the gradient at startup
function clikGrad(e) { // clickOnGradient
rmTrash(e); // removeTrashcan
addC(Math.round((e.offsetX/gLen)*256));
function clikOnGradient(e) {
removeTrashcan(e);
addC(Math.round((e.offsetX/gradientLength)*256));
}
///////// Add a new color marker
function addC(tPos, thisCol = '') {
let pos = -1;
let exist = false;
const cMarks = gBox.querySelectorAll('.cMark'); // color markers
///////// Add a new colorMarker
function addC(truePos, thisColor = '') {
let position = -1;
let iExist = false;
const colorMarkers = gradientBox.querySelectorAll('.color-marker');
cMarks.forEach((cm) => {
if (cm.getAttribute("data-tpos") == tPos) exist = true;
colorMarkers.forEach((colorMarker, i) => {
if (colorMarker.getAttribute("data-truepos") == truePos) {
iExist = true;
}
});
if (cMarks.length > 17) exist = true;
if (exist) return;
if (tPos > 0 && tPos < 255) {
for (var i=1; i<=16 && pos<1; i++) {
if (!gId("cMark"+i)) pos = i;
if (colorMarkers.length > 17) iExist = true;
if (iExist) return; // Exit the function early if iExist is true
if (truePos > 0 && truePos < 255) {
//calculate first available > 0
for (var i = 1; i <= 16 && position < 1; i++) {
if (!gId("colorMarker"+i)) {
position = i;
}
}
} else {
pos = tPos;
} else{
position = truePos;
}
if (thisCol == '') {
thisCol = `#${(Math.random()*0xFFFFFF<<0).toString(16).padStart(6,'0')}`;
if (thisColor == ''){
thisColor = `#${(Math.random() * 0xFFFFFF << 0).toString(16).padStart(6, '0')}`;// set random color as default
}
const colorMarker = cE('span'); // create a marker for the color position
colorMarker.className = 'color-marker';
colorMarker.id = 'colorMarker' + position.toString();
colorMarker.setAttribute("data-truepos", truePos); //Used to always have a true position no matter what screen or percentage we use
colorMarker.setAttribute("data-truecol", thisColor); //Used to hold the color of the position in the gradient connected to a true position
colorMarker.setAttribute("data-offset", mOffs);
colorMarker.addEventListener('click', stopFurtherProp); //Added to prevent the gradient click to fire when covered by a marker
colorMarker.style.left = `${Math.round((gradientLength / 256) * truePos)+mOffs}px`;
const colorPicker = cE('input');
colorPicker.type = 'color';
colorPicker.value = thisColor;
colorPicker.className = 'color-picker';
colorPicker.id = 'colorPicker' + position.toString();
colorPicker.addEventListener('input', updateGradient);
colorPicker.addEventListener('click',cpClk)
const colorPickerMarker = cE('span'); // create a marker for the color position
colorPickerMarker.className = 'color-picker-marker';
colorPickerMarker.id = 'colorPickerMarker' + position.toString();
colorPickerMarker.addEventListener('click', colClk);
colorPickerMarker.style.left = colorMarker.style.left;
colorPicker.style.left = colorMarker.style.left;
const deleteMarker = cE('span'); // create a delete marker for the color position
if (position > 0 && position < 255) {
deleteMarker.className = 'delete-marker';
deleteMarker.id = 'deleteMarker' + position.toString();
deleteMarker.addEventListener('click', (e) => {
deleteColor(e);
});
deleteMarker.style.left = colorMarker.style.left
}
const cMark = cE('span'); // color marker
cMark.className = 'cMark';
cMark.id = 'cMark' + pos;
cMark.setAttribute("data-tpos", tPos);
cMark.setAttribute("data-tcol", thisCol);
cMark.setAttribute("data-offset", mOffs);
cMark.addEventListener('click', stopProp);
cMark.style.left = `${Math.round((gLen/256)*tPos)+mOffs}px`;
colorMarker.style.backgroundColor = colorPicker.value; // set marker color to match color picker
colorPickerMarker.style.backgroundColor = colorPicker.value;
const cPick = cE('input'); // colorPicker
cPick.type = 'color';
cPick.value = thisCol;
cPick.className = 'cPick';
cPick.id = 'cPick' + pos;
cPick.addEventListener('input', updGrad);
cPick.addEventListener('click', cpClk);
gradientBox.appendChild(colorPicker);
gradientBox.appendChild(colorMarker);
gradientBox.appendChild(colorPickerMarker);
if (position != 0 && position != 255) gradientBox.appendChild(deleteMarker); // append the marker if not start or end
//make markers slidable IF they are not the first or last slider
if (position > 0 && position < 255) makeMeDrag(gId(colorMarker.id));
const cPM = cE('span'); // colorPickerMarker
cPM.className = 'cPickMark';
cPM.id = 'cPM' + pos;
cPM.addEventListener('click', colClk);
cPM.style.left = cMark.style.left;
cPick.style.left = cMark.style.left;
setTooltipMarker(gId(colorMarker.id));
if (pos > 0 && pos < 255) {
const dMark = cE('span'); // deleteMarker
dMark.className = 'dMark';
dMark.id = 'dMark' + pos;
dMark.addEventListener('click', (e) => { delCol(e); });
dMark.style.left = cMark.style.left;
gBox.appendChild(dMark);
}
cMark.style.backgroundColor = cPick.value;
cPM.style.backgroundColor = cPick.value;
gBox.appendChild(cPick);
gBox.appendChild(cMark);
gBox.appendChild(cPM);
if (pos > 0 && pos < 255) mkDrag(gId(cMark.id)); // makeMeDrag
setTip(gId(cMark.id)); // setTooltipMarker
updGrad();
updateGradient();
}
///////// Update Gradient
function updGrad() { // updateGradient
const cMarks = gBox.querySelectorAll('.cMark');
function updateGradient() {
const colorMarkers = gradientBox.querySelectorAll('.color-marker');
pxCol = {};
tCol = {};
cMarks.forEach((cm) => {
const cp = gId(cm.id.replace('cMark','cPick'));
const col = cp.value;
gId(cm.id.replace('cMark','cPM')).style.backgroundColor = col;
cm.style.backgroundColor = col;
cm.setAttribute("data-tcol", col);
const tPos = cm.getAttribute("data-tpos");
const gPos = Math.round((gLen/256)*tPos);
pxCol[gPos] = col;
tCol[tPos] = col;
tCol = {}
colorMarkers.forEach((colorMarker, index) => {
const thisColorPicker = gId(colorMarkers[index].id.replace('colorMarker', 'colorPicker'));
const colorToSet = thisColorPicker.value;
gId(colorMarkers[index].id.replace('colorMarker', 'colorPickerMarker')).style.backgroundColor = colorToSet;
colorMarkers[index].style.backgroundColor = colorToSet;
colorMarkers[index].setAttribute("data-truecol", colorToSet);
const tPos = colorMarkers[index].getAttribute("data-truepos");
const gradientPos = Math.round((gradientLength / 256)*tPos);
pxCol[gradientPos] = colorToSet;
tCol[tPos] = colorToSet;
});
let gStr = 'linear-gradient(to right';
Object.entries(pxCol).forEach(([p,c]) => {
gStr += `, ${c} ${p}px`;
gradientString = 'linear-gradient(to right';
Object.entries(pxCol).forEach(([p, c]) => {
gradientString += `, ${c} ${p}px`;
});
gStr += ')';
gBox.style.background = gStr;
gradientString += ')';
gradientBox.style.background = gradientString;
//gId("jsonstring").innerHTML = calcJSON();
}
function stopProp(e) { e.stopPropagation(); }
function colClk(e) {
rmTrash(e);
function stopFurtherProp(e) {
e.stopPropagation();
const src = e.target || e.srcElement;
let cp = gId(src.id.replace("cPM","cPick"));
}
function colClk(e){
removeTrashcan(e)
e.stopPropagation();
let cp = gId(e.srcElement.id.replace("Marker",""));
cp.click();
}
function cpClk(e) {
rmTrash(e);
removeTrashcan(event)
e.stopPropagation();
}
// make element draggable
function mkDrag(el) { // makeMeDrag
var posNew=0, mPos=0;
var rect=gBox.getBoundingClientRect();
var maxX=rect.right, minX=rect.left, gLen=maxX-minX+1;
//This neat little function makes any element draggable on the X-axis.
//Just call: makeMeDrag(myElement); And you are good to go.
function makeMeDrag(elmnt) {
var posNew = 0, mousePos = 0, mouseOffset = 0
//Set these to whatever you want to limit your movement to
var rect = gradientBox.getBoundingClientRect();
var maxX = rect.right; // maximum X coordinate
var minX = rect.left; // minimum X coordinate i.e. also offset from left of screen
var gradientLength = maxX - minX + 1;
el.onmousedown=dragStart;
el.ontouchstart=dragStart;
elmnt.onmousedown = dragMouseDown;
elmnt.ontouchstart = dragMouseDown;
function dragStart(e) {
rmTrash(e);
var isT=e.type.startsWith('touch');
if (!isT) e.preventDefault();
mPos=isT?e.touches[0].clientX:e.clientX;
d.onmouseup=dragEnd; d.ontouchend=dragEnd; d.ontouchcancel=dragEnd;
d.onmousemove=dragMove; d.ontouchmove=dragMove;
function dragMouseDown(e) {
removeTrashcan(event)
e = e || window.event;
var isTouch = e.type.startsWith('touch');
if (!isTouch) e.preventDefault();
// get the mouse cursor position at startup:
mousePos = isTouch ? e.touches[0].clientX : e.clientX;
d.onmouseup = closeDragElement;
d.ontouchcancel = closeDragElement;
d.ontouchend = closeDragElement;
// call a function whenever the cursor moves:
d.onmousemove = elementDrag;
d.ontouchmove = elementDrag;
}
function dragMove(e) {
var isT=e.type.startsWith('touch');
if (!isT) e.preventDefault();
var cX=isT?e.touches[0].clientX:e.clientX;
posNew=mPos-cX; mPos=cX;
var mInG=mPos-(minX+1);
var tPos=Math.round((mInG/gLen)*256);
var old=el.getAttribute("data-tpos");
if (tPos>0 && tPos<255 && old!=tPos) {
el.style.left=(Math.round((gLen/256)*tPos)+mOffs)+"px";
gId(el.id.replace('cMark','cPM')).style.left=el.style.left;
gId(el.id.replace('cMark','dMark')).style.left=el.style.left;
gId(el.id.replace('cMark','cPick')).style.left=el.style.left;
el.setAttribute("data-tpos",tPos);
setTip(el);
updGrad();
function elementDrag(e) {
e = e || window.event;
var isTouch = e.type.startsWith('touch');
if (!isTouch) e.preventDefault();
// calculate the new cursor position:
var clientX = isTouch ? e.touches[0].clientX : e.clientX;
posNew = mousePos - clientX;
mousePos = clientX;
mousePosInGradient = mousePos - (minX + 1)
truePos = Math.round((mousePosInGradient/gradientLength)*256);
oldTruePos = elmnt.getAttribute("data-truepos");
// set the element's new position if new position within min/max limits:
if (truePos > 0 && truePos < 255 && oldTruePos != truePos) {
if (truePos < 64) {
thisOffset = 0;
} else if (truePos > 192) {
thisOffset = 7;
} else {
thisOffset=3;
}
elmnt.style.left = (Math.round((gradientLength/256)*truePos)+mOffs) + "px";
gId(elmnt.id.replace('colorMarker', 'colorPickerMarker')).style.left = elmnt.style.left;
gId(elmnt.id.replace('colorMarker', 'deleteMarker')).style.left = elmnt.style.left;
gId(elmnt.id.replace('colorMarker', 'colorPicker')).style.left = elmnt.style.left;
elmnt.setAttribute("data-truepos", truePos);
setTooltipMarker(elmnt);
updateGradient();
}
}
function dragEnd() {
d.onmouseup=null; d.ontouchend=null; d.ontouchcancel=null;
d.onmousemove=null; d.ontouchmove=null;
function closeDragElement() {
/* stop moving when mouse button is released:*/
d.onmouseup = null;
d.ontouchcancel = null;
d.ontouchend = null;
d.onmousemove = null;
d.ontouchmove = null;
}
}
function setTip(el) { // setTooltipMarker
el.setAttribute('title', `${el.getAttribute("data-tpos")} : ${el.getAttribute("data-tcol")}`);
function setTooltipMarker(elmnt) {
elmnt.setAttribute('title', `${elmnt.getAttribute("data-truepos")} : ${elmnt.getAttribute("data-truecol")}`)
}
function delCol(e) { // deleteColor
var trash=cE("div");
var dM=e.target || e.srcElement;
var cM=gId(dM.id.replace("d","c"));
var cPM=gId(dM.id.replace("dMark","cPM"));
var cP=gId(dM.id.replace("dMark","cPick"));
var rX=dM.getBoundingClientRect().x-10;
var rY=dM.getBoundingClientRect().y+13;
trash.id="trash";
trash.innerHTML=svgTrash;
trash.style.position="absolute";
trash.style.left=rX+"px";
trash.style.top=rY+"px";
function deleteColor(e) {
var trash = cE("div");
thisDeleteMarker = e.srcElement;
thisColorMarker = gId(thisDeleteMarker.id.replace("delete", "color"));
thisColorPickerMarker = gId(thisDeleteMarker.id.replace("delete", "colorPicker"));
thisColorPicker = gId(thisDeleteMarker.id.replace("deleteMarker", "colorPicker"));
renderOffsetX = 15 - 5;
renderX = e.srcElement.getBoundingClientRect().x - renderOffsetX;
renderY = e.srcElement.getBoundingClientRect().y + 13;
trash.id = "trash";
trash.innerHTML = svgTrash;
trash.style.position = "absolute";
trash.style.left = (renderX) + "px";
trash.style.top = (renderY) + "px";
d.body.appendChild(trash);
trash.addEventListener("click",()=>{
trash.remove(); cM.remove(); cPM.remove(); cP.remove(); dM.remove();
updGrad();
trash.addEventListener("click", (e)=>{
trash.parentNode.removeChild(trash);
thisDeleteMarker.parentNode.removeChild(thisDeleteMarker);
thisColorPickerMarker.parentNode.removeChild(thisColorPickerMarker);
thisColorMarker.parentNode.removeChild(thisColorMarker);
thisColorPicker.parentNode.removeChild(thisColorPicker);
updateGradient();
});
e.stopPropagation();
d.addEventListener("click", rmTrash);
// Add event listener to close the trashcan on outside click
d.addEventListener("click", removeTrashcan);
e.stopPropagation();
}
function rmTrash(e) { // removeTrashcan
var t=gId("trash");
if (t && e.target!=t) { t.remove(); d.removeEventListener("click", rmTrash);}
function removeTrashcan(event) {
trash = gId("trash");
if (event.target != trash && trash) {
trash.parentNode.removeChild(trash);
d.removeEventListener("click", removeTrashcan);
}
}
function chkW() {
const wrap=gId('wrap'); const head=gId('head');
head.style.display=(wrap.offsetWidth<600)?'none':'inline';
//Possibly add more code that recalculates the gradient... Massive job ;)
const wrap = gId('wrap');
const head = gId('head');
if (wrap.offsetWidth < 600) {
head.style.display = 'none';
} else {
head.style.display = 'inline';
}
}
function calcJSON() {
let rStr='{"palette":[';
Object.entries(tCol).forEach(([p,c],i)=>{
if (i>0) rStr+=',';
rStr+=`${p},"${c.slice(1)}"`;
let rStr = '{"palette":['
Object.entries(tCol).forEach(([p, c]) => {
if (p > 0) rStr += ',';
rStr += `${p},"${c.slice(1)}"`; // store in hex notation
//rStr += `${p},${parseInt(c.slice(1, 3), 16)},${parseInt(c.slice(3, 5), 16)},${parseInt(c.slice(5, 7), 16)}`;
});
rStr+=']}';
rStr += ']}';
return rStr;
}
function initUpload(i) {
uploadJSON(calcJSON(), `/palette${i}.json`);
function initiateUpload(idx) {
const data = calcJSON();
const fileName = `/palette${idx}.json`;
uploadJSON(data, fileName);
}
function uploadJSON(jsonString, fileName) {
@@ -425,38 +525,41 @@
}
async function getInfo() {
getLoc();
try {
var arr = [];
const resInfo = await fetch(getURL('/json/info')); // fetch info (includes cpalcount and cpalmax)
const resPals = await fetch(getURL('/json/pal')); // fetch palette names
const json = await resInfo.json();
palNm = await resPals.json();
cpalc = json.cpalcount;
cpalm = json.cpalmax;
fetchPals(cpalc-1);
} catch (error) {
console.error(error);
hst = location.host;
if (hst.length > 0 ) {
try {
var arr = [];
const responseInfo = await fetch('http://'+hst+'/json/info');
const responsePalettes = await fetch('http://'+hst+'/json/palettes');
const json = await responseInfo.json();
paletteName = await responsePalettes.json();
cpalc = json.cpalcount;
fetchPalettes(cpalc-1);
} catch (error) {
console.error(error);
}
} else {
console.error('cannot identify host');
}
}
async function fetchPals(lastPal) {
palArr.length = 0;
async function fetchPalettes(lastPal) {
paletteArray.length = 0;
for (let i = 0; i <= lastPal; i++) {
const url = getURL(`/palette${i}.json`);
const url = `http://${hst}/palette${i}.json`;
try {
const response = await fetch(url);
const json = await response.json();
palArr.push(json);
paletteArray.push(json);
} catch (error) {
cpalc--; //remove audio/dynamically generated palettes
console.error(`Error fetching JSON from ${url}: `, error);
}
}
//If there is room for more custom palettes, add an empty, gray slot
if (palArr.length < cpalm) {
if (paletteArray.length < 10) {
//Room for one more :)
palArr.push({"palette":[0,70,70,70,255,70,70,70]});
paletteArray.push({"palette":[0,70,70,70,255,70,70,70]});
}
//Get static palettes from localStorage and do some magic to reformat them into the same format as the palette JSONs
@@ -466,12 +569,12 @@
const wledPalx = JSON.parse(localStorage.getItem('wledPalx'));
if (!wledPalx) {
alert("Palette cache missing from browser. Return to main page first.","Missing cache!")
alert("The cache of palettes are missig from your browser. You should probably return to the main page and let it load properly for the palettes cache to regenerate before returning here.","Missing cached palettes!")
} else {
for (const key in wledPalx.p) {
wledPalx.p[key].name = palNm[key];
if (key > 255-cpalm) {
delete wledPalx.p[key]; // remove custom palettes
wledPalx.p[key].name = paletteName[key];
if (key > 245) {
delete wledPalx.p[key];
continue;
}
const arr = wledPalx.p[key];
@@ -507,133 +610,130 @@
// Sort pArray by name
pArray.sort((a, b) => a.name.localeCompare(b.name));
palArr.push( ...pArray);
paletteArray.push( ...pArray);
}
genPalDivs();
generatePaletteDivs();
}
function genPalDivs() {
const palsDiv = gId("pals");
const sPalsDiv = gId("sPals");
const memWarn = gId("memWarn");
const palDivs = Array.from(palsDiv.children).filter((child) => {
return /^pal\d+$/.test(child.id); // match ids "pal" followed by one or more digits
function generatePaletteDivs() {
const palettesDiv = gId("palettes");
const staticPalettesDiv = gId("staticPalettes");
const paletteDivs = Array.from(palettesDiv.children).filter((child) => {
return child.id.match(/^palette\d$/); // match only elements with id starting with "palette" followed by a single digit
});
for (const div of palDivs) {
palsDiv.removeChild(div); // remove each div that matches the above selector
for (const div of paletteDivs) {
palettesDiv.removeChild(div); // remove each div that matches the above selector
}
memWarn.style.display = (cpalc >= 10) ? 'block' : 'none'; // Show/hide memory warning based on custom palette count
for (let i = 0; i < paletteArray.length; i++) {
const palette = paletteArray[i];
const paletteDiv = cE("div");
paletteDiv.id = `palette${i}`;
paletteDiv.classList.add("palette");
const thisKey = Object.keys(palette)[0];
paletteDiv.dataset.colarray = JSON.stringify(palette[thisKey]);
for (let i = 0; i < palArr.length; i++) {
const pal = palArr[i];
const palDiv = cE("div");
palDiv.id = `pal${i}`;
palDiv.classList.add("pal");
const thisKey = Object.keys(pal)[0];
palDiv.dataset.colarray = JSON.stringify(pal[thisKey]);
const gradientDiv = cE("div");
gradientDiv.id = `paletteGradient${i}`
const buttonsDiv = cE("div");
buttonsDiv.id = `buttonsDiv${i}`;
buttonsDiv.classList.add("buttonsDiv")
const gradDiv = cE("div");
gradDiv.id = `pGrad${i}`
const btnsDiv = cE("div");
btnsDiv.id = `btns${i}`;
btnsDiv.classList.add("btnsDiv")
const sSpan = cE("span");
sSpan.id = `s${i}`;
sSpan.onclick = function() {initUpload(i)};
sSpan.setAttribute('title', `Send current editor to slot ${i}`); // perhaps Save instead of Send?
sSpan.innerHTML = svgSave;
sSpan.classList.add("sSpan")
const eSpan = cE("span");
eSpan.id = `e${i}`;
eSpan.onclick = function() {loadEdit(i)};
eSpan.setAttribute('title', `Copy slot ${i} to editor`);
if (palArr[i].name) {
eSpan.setAttribute('title', `Copy ${palArr[i].name} to editor`);
const sendSpan = cE("span");
sendSpan.id = `sendSpan${i}`;
sendSpan.onclick = function() {initiateUpload(i)};
sendSpan.setAttribute('title', `Send current editor to slot ${i}`); // perhaps Save instead of Send?
sendSpan.innerHTML = svgSave;
sendSpan.classList.add("sendSpan")
const editSpan = cE("span");
editSpan.id = `editSpan${i}`;
editSpan.onclick = function() {loadForEdit(i)};
editSpan.setAttribute('title', `Copy slot ${i} palette to editor`);
if (paletteArray[i].name) {
editSpan.setAttribute('title', `Copy ${paletteArray[i].name} palette to editor`);
}
eSpan.innerHTML = svgEdit;
eSpan.classList.add("eSpan")
editSpan.innerHTML = svgEdit;
editSpan.classList.add("editSpan")
gradDiv.classList.add("pGrads");
let gCols = "";
gradientDiv.classList.add("paletteGradients");
let gradientColors = "";
for (let j = 0; j < pal[thisKey].length; j += 2) {
const pos = pal[thisKey][j];
if (typeof(pal[thisKey][j+1]) === "string") {
gCols += `#${pal[thisKey][j+1]} ${pos/255*100}%, `;
for (let j = 0; j < palette[thisKey].length; j += 2) {
const position = palette[thisKey][j];
if (typeof(palette[thisKey][j+1]) === "string") {
gradientColors += `#${palette[thisKey][j+1]} ${position/255*100}%, `;
} else {
const r = pal[thisKey][j + 1];
const g = pal[thisKey][j + 2];
const b = pal[thisKey][j + 3];
gCols += `rgba(${r}, ${g}, ${b}, 1) ${pos/255*100}%, `;
const red = palette[thisKey][j + 1];
const green = palette[thisKey][j + 2];
const blue = palette[thisKey][j + 3];
gradientColors += `rgba(${red}, ${green}, ${blue}, 1) ${position/255*100}%, `;
j += 2;
}
}
gCols = gCols.slice(0, -2); // remove the last comma and space
gradDiv.style.backgroundImage = `linear-gradient(to right, ${gCols})`;
palDiv.className = "pGradPar";
gradientColors = gradientColors.slice(0, -2); // remove the last comma and space
gradientDiv.style.backgroundImage = `linear-gradient(to right, ${gradientColors})`;
paletteDiv.className = "palGradientParent";
if (thisKey == "palette") {
btnsDiv.appendChild(sSpan); //Only offer to send to custom palettes
buttonsDiv.appendChild(sendSpan); //Only offer to send to custom palettes
} else{
eSpan.style.marginLeft = "25px";
editSpan.style.marginLeft = "25px";
}
if (i!=cpalc) {
btnsDiv.appendChild(eSpan); //Dont offer to edit the empty spot
buttonsDiv.appendChild(editSpan); //Dont offer to edit the empty spot
}
palDiv.appendChild(gradDiv);
palDiv.appendChild(btnsDiv);
paletteDiv.appendChild(gradientDiv);
paletteDiv.appendChild(buttonsDiv);
if (thisKey == "palette") {
palsDiv.appendChild(palDiv);
palettesDiv.appendChild(paletteDiv);
} else {
sPalsDiv.appendChild(palDiv);
staticPalettesDiv.appendChild(paletteDiv);
}
}
}
function loadEdit(i) {
d.querySelectorAll('input[id^="cPick"]').forEach((input) => {
function loadForEdit(i) {
d.querySelectorAll('input[id^="colorPicker"]').forEach((input) => {
input.parentNode.removeChild(input);
});
d.querySelectorAll('span[id^="cMark"], span[id^="cPM"], span[id^="dMark"]').forEach((span) => {
d.querySelectorAll('span[id^="colorMarker"], span[id^="colorPickerMarker"], span[id^="deleteMarker"]').forEach((span) => {
span.parentNode.removeChild(span);
});
let colArr = JSON.parse(gId(`pal${i}`).getAttribute("data-colarray"));
for (let j = 0; j < colArr.length; j += 2) {
const pos = colArr[j];
let colArray = JSON.parse(gId(`palette${i}`).getAttribute("data-colarray"));
for (let j = 0; j < colArray.length; j += 2) {
const position = colArray[j];
let hex;
if (typeof(colArr[j+1]) === "string") {
hex = `#${colArr[j+1]}`;
if (typeof(colArray[j+1]) === "string") {
hex = `#${colArray[j+1]}`;
} else {
const r = colArr[j + 1];
const g = colArr[j + 2];
const b = colArr[j + 3];
hex = rgbToHex(r, g, b);
const red = colArray[j + 1];
const green = colArray[j + 2];
const blue = colArray[j + 3];
hex = rgbToHex(red, green, blue);
j += 2;
}
addC(pos, hex);
addC(position, hex);
window.scroll(0, 0);
}
}
function distrib() {
let cMarks = [...gBox.querySelectorAll('.cMark')];
cMarks.sort((a, b) => a.getAttribute('data-tpos') - b.getAttribute('data-tpos'));
cMarks = cMarks.slice(1, -1);
const spacing = Math.round(256 / (cMarks.length + 1));
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));
cMarks.forEach((e, i) => {
const mId = e.id.match(/\d+/)[0];
const tCol = e.getAttribute("data-tcol");
gBox.removeChild(e);
gBox.removeChild(gId(`cPick${mId}`));
gBox.removeChild(gId(`cPM${mId}`));
gBox.removeChild(gId(`dMark${mId}`));
addC(spacing * (i + 1), tCol);
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);
});
}

View File

@@ -668,9 +668,20 @@ function parseInfo(i) {
if (loc) name = "(L) " + name;
d.title = name;
simplifiedUI = i.simplifiedui;
ledCount = i.leds.count;
// Add safety checks for LED count data to prevent UI crashes
if (i.leds && typeof i.leds.count !== 'undefined') {
ledCount = i.leds.count;
} else {
console.warn('LED count data missing, using fallback value');
ledCount = 30; // Fallback value matching firmware default
}
//syncTglRecv = i.str;
maxSeg = i.leds.maxseg;
if (i.leds && typeof i.leds.maxseg !== 'undefined') {
maxSeg = i.leds.maxseg;
} else {
console.warn('Max segment data missing, using fallback value');
maxSeg = 16; // Reasonable fallback for max segments
}
pmt = i.fs.pmt;
if (pcMode && !i.wifi.ap) gId('edit').classList.remove("hide"); else gId('edit').classList.add("hide");
gId('buttonNodes').style.display = lastinfo.ndc > 0 ? null:"none";
@@ -912,12 +923,24 @@ function populateSegments(s)
gId(`segr${i}`).classList.add("hide");
}
if (segCount < 2) {
gId(`segd${lSeg}`).classList.add("hide"); // hide delete if only one segment
if (parseInt(gId("seg0bri").value)==255) gId(`segp0`).classList.add("hide");
// Add safety check for segment elements to prevent UI crashes
const segdElement = gId(`segd${lSeg}`);
if (segdElement) segdElement.classList.add("hide"); // hide delete if only one segment
const seg0briElement = gId("seg0bri");
const segp0Element = gId(`segp0`);
if (seg0briElement && segp0Element && parseInt(seg0briElement.value)==255) segp0Element.classList.add("hide");
// hide segment controls if there is only one segment in simplified UI
if (simplifiedUI) gId("segcont").classList.add("hide");
}
if (!isM && !noNewSegs && (cfg.comp.seglen?parseInt(gId(`seg${lSeg}s`).value):0)+parseInt(gId(`seg${lSeg}e`).value)<ledCount) gId(`segr${lSeg}`).classList.remove("hide");
// Add safety checks for segment control elements
const segSElement = gId(`seg${lSeg}s`);
const segEElement = gId(`seg${lSeg}e`);
const segrElement = gId(`segr${lSeg}`);
if (!isM && !noNewSegs && segSElement && segEElement && segrElement) {
const segLen = cfg.comp.seglen ? parseInt(segSElement.value) : 0;
const segEnd = parseInt(segEElement.value);
if (segLen + segEnd < ledCount) segrElement.classList.remove("hide");
}
gId('segutil2').style.display = (segCount > 1) ? "block":"none"; // rsbtn parent
if (Array.isArray(li.maps) && li.maps.length>1) {
@@ -1042,7 +1065,8 @@ function genPalPrevCss(id)
}
var gradient = [];
paletteData.forEach((e,j) => {
for (let j = 0; j < paletteData.length; j++) {
const e = paletteData[j];
let r, g, b;
let index = false;
if (Array.isArray(e)) {
@@ -1064,8 +1088,9 @@ function genPalPrevCss(id)
if (index === false) {
index = Math.round(j / paletteData.length * 100);
}
gradient.push(`rgb(${r},${g},${b}) ${index}%`);
});
}
return `background: linear-gradient(to right,${gradient.join()});`;
}
@@ -2251,7 +2276,9 @@ function rptSeg(s)
var rev = gId(`seg${s}rev`).checked;
var mi = gId(`seg${s}mi`).checked;
var sel = gId(`seg${s}sel`).checked;
var pwr = gId(`seg${s}pwr`).classList.contains('act');
// Add safety check for segment power element to prevent UI crashes
const segPwrElement = gId(`seg${s}pwr`);
var pwr = segPwrElement ? segPwrElement.classList.contains('act') : false;
var obj = {"seg": {"id": s, "n": name, "start": start, "stop": (cfg.comp.seglen?start:0)+stop, "rev": rev, "mi": mi, "on": pwr, "bri": parseInt(gId(`seg${s}bri`).value), "sel": sel}};
if (gId(`seg${s}grp`)) {
var grp = parseInt(gId(`seg${s}grp`).value);
@@ -2378,7 +2405,13 @@ function setGrp(s, g)
function setSegPwr(s)
{
var pwr = gId(`seg${s}pwr`).classList.contains('act');
// Add safety check for segment power element to prevent UI crashes
const segPwrElement = gId(`seg${s}pwr`);
if (!segPwrElement) {
console.warn('Segment power element not found, skipping power toggle');
return;
}
var pwr = segPwrElement.classList.contains('act');
var obj = {"seg": {"id": s, "on": !pwr}};
requestJson(obj);
}
@@ -3086,9 +3119,11 @@ function unify(e) { return e.changedTouches ? e.changedTouches[0] : e; }
function hasIroClass(classList)
{
let found = false;
classList.forEach((e)=>{ if (e.startsWith('Iro')) found = true; });
return found;
for (var i = 0; i < classList.length; i++) {
var element = classList[i];
if (element.startsWith('Iro')) return true;
}
return false;
}
//required by rangetouch.js
function lock(e)

View File

@@ -17,8 +17,8 @@
position: absolute;
}
</style>
<script src="common.js"></script>
<script>
var d = document;
var ws;
var tmout = null;
var c;
@@ -62,14 +62,32 @@
if (window.location.href.indexOf("?ws") == -1) {update(); return;}
// Initialize WebSocket connection
ws = connectWs(function () {
//console.info("Peek WS open");
ws.send('{"lv":true}');
});
try {
ws = top.window.ws;
} catch (e) {}
if (ws && ws.readyState === WebSocket.OPEN) {
//console.info("Peek uses top WS");
ws.send("{'lv':true}");
} else {
//console.info("Peek WS opening");
let l = window.location;
let pathn = l.pathname;
let paths = pathn.slice(1,pathn.endsWith('/')?-1:undefined).split("/");
let url = l.origin.replace("http","ws");
if (paths.length > 1) {
url += "/" + paths[0];
}
ws = new WebSocket(url+"/ws");
ws.onopen = function () {
//console.info("Peek WS open");
ws.send("{'lv':true}");
}
}
ws.binaryType = "arraybuffer";
ws.addEventListener('message', (e) => {
try {
if (toString.call(e.data) === '[object ArrayBuffer]') {
let leds = new Uint8Array(e.data);
let leds = new Uint8Array(event.data);
if (leds[0] != 76) return; //'L'
// leds[1] = 1: 1D; leds[1] = 2: 1D/2D (leds[2]=w, leds[3]=h)
draw(leds[1]==2 ? 4 : 2, 3, leds, (a,i) => `rgb(${a[i]},${a[i+1]},${a[i+2]})`);
@@ -84,4 +102,4 @@
<body onload="S()">
<canvas id="canv"></canvas>
</body>
</html>
</html>

View File

@@ -10,7 +10,6 @@
margin: 0;
}
</style>
<script src="common.js"></script>
</head>
<body>
<canvas id="canv"></canvas>
@@ -27,13 +26,30 @@
var ctx = c.getContext('2d');
if (ctx) { // Access the rendering context
// use parent WS or open new
var ws = connectWs(()=>{
ws.send('{"lv":true}');
});
var ws;
try {
ws = top.window.ws;
} catch (e) {}
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send("{'lv':true}");
} else {
let l = window.location;
let pathn = l.pathname;
let paths = pathn.slice(1,pathn.endsWith('/')?-1:undefined).split("/");
let url = l.origin.replace("http","ws");
if (paths.length > 1) {
url += "/" + paths[0];
}
ws = new WebSocket(url+"/ws");
ws.onopen = ()=>{
ws.send("{'lv':true}");
}
}
ws.binaryType = "arraybuffer";
ws.addEventListener('message',(e)=>{
try {
if (toString.call(e.data) === '[object ArrayBuffer]') {
let leds = new Uint8Array(e.data);
let leds = new Uint8Array(event.data);
if (leds[0] != 76 || leds[1] != 2 || !ctx) return; //'L', set in ws.cpp
let mW = leds[2]; // matrix width
let mH = leds[3]; // matrix height

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<title>WLED Settings</title>
<script src="common.js" type="text/javascript"></script>
<script src="common.js" async type="text/javascript"></script>
<script>
function S() {
getLoc();

View File

@@ -4,7 +4,7 @@
<meta charset="utf-8">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<title>2D Set-up</title>
<script src="common.js" type="text/javascript"></script>
<script src="common.js" async type="text/javascript"></script>
<script>
var maxPanels=64;
var ctx = null;

View File

@@ -4,7 +4,7 @@
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<meta charset="utf-8">
<title>DMX Settings</title>
<script src="common.js" type="text/javascript"></script>
<script src="common.js" async type="text/javascript"></script>
<script>
function HW(){window.open("https://kno.wled.ge/interfaces/dmx-output/");}
function GCH(num) {

View File

@@ -4,7 +4,7 @@
<meta charset="utf-8">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<title>LED Settings</title>
<script src="common.js" type="text/javascript"></script>
<script src="common.js" async type="text/javascript"></script>
<script>
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 customStarts=false,startsDirty=[];
@@ -17,7 +17,6 @@
function isD2P(t) { return gT(t).t === "2P"; } // is digital 2 pin type
function isNet(t) { return gT(t).t === "N"; } // is network type
function isVir(t) { return gT(t).t === "V" || isNet(t); } // is virtual type
function isHub75(t){ return gT(t).t === "H"; } // is HUB75 type
function hasRGB(t) { return !!(gT(t).c & 0x01); } // has RGB
function hasW(t) { return !!(gT(t).c & 0x02); } // has white channel
function hasCCT(t) { return !!(gT(t).c & 0x04); } // is white CCT enabled
@@ -25,7 +24,6 @@
function mustR(t) { return !!(gT(t).c & 0x20); } // Off refresh is mandatory
function numPins(t){ return Math.max(gT(t).t.length, 1); } // type length determines number of GPIO pins
function chrID(x) { return String.fromCharCode((x<10?48:55)+x); }
function toNum(c) { let n=c.charCodeAt(0); return (n>=48 && n<=57)?n-48:(n>=65 && n<=90)?n-55:0; } // convert char (0-9A-Z) to number (0-35)
function S() {
getLoc();
loadJS(getURL('/settings/s.js?p=2'), false, ()=>{
@@ -45,13 +43,13 @@
}
function bLimits(b,v,p,m,l,o=5,d=2,a=6) {
maxB = b; // maxB - max physical (analog + digital) buses: 32 - ESP32, 14 - S3/S2, 6 - C3, 4 - 8266
maxD = d; // maxD - max digital channels (can be changed if using ESP32 parallel I2S): 16 - ESP32, 12 - S3/S2, 2 - C3, 3 - 8266
maxA = a; // maxA - max analog channels: 16 - ESP32, 8 - S3/S2, 6 - C3, 5 - 8266
maxV = v; // maxV - min virtual buses: 6 - ESP32/S3, 4 - S2/C3, 3 - ESP8266 (only used to distinguish S2/S3)
maxPB = p; // maxPB - max LEDs per bus
maxM = m; // maxM - max LED memory
maxL = l; // maxL - max LEDs (will serve to determine ESP >1664 == ESP32)
maxCO = o; // maxCO - max Color Order mappings
maxD = d; // maxD - max digital channels (can be changed if using ESP32 parallel I2S): 16 - ESP32, 12 - S3/S2, 2 - C3, 3 - 8266
maxA = a; // maxA - max analog channels: 16 - ESP32, 8 - S3/S2, 6 - C3, 5 - 8266
}
function is8266() { return maxA == 5 && maxD == 3; } // NOTE: see const.h
function is32() { return maxA == 16 && maxD == 16; } // NOTE: see const.h
@@ -63,44 +61,45 @@
var nList = d.Sf.querySelectorAll("#mLC input[name^=L]");
nList.forEach((LC,i)=>{
if (!ok) return; // prevent iteration after conflict
let nm = LC.name.substring(0,2); // field name : /L./
if (nm.search(/^L[0-4]/) < 0) return; // not pin fields
let n = LC.name.substring(2,3); // bus number (0-Z)
let nm = LC.name.substring(0,2);
let n = LC.name.substring(2);
let t = parseInt(d.Sf["LT"+n].value, 10); // LED type SELECT
if(isHub75(t)) {
return;
}
// ignore IP address
if (isNet(t)) return;
if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3") {
if (isNet(t)) return;
}
//check for pin conflicts
if (LC.value!="" && LC.value!="-1") {
let p = d.rsvd.concat(d.um_p); // used pin array
d.Sf.querySelectorAll("select.pin").forEach((e)=>{if(e.value>-1)p.push(parseInt(e.value));}) // buttons, IR & relay
if (p.some((e)=>e==parseInt(LC.value))) {
alert(`Sorry, pins ${JSON.stringify(p)} can't be used.`);
LC.value="";
LC.focus();
ok = false;
return;
} else if (d.ro_gpio.some((e)=>e==parseInt(LC.value))) {
alert(`Sorry, pins ${JSON.stringify(d.ro_gpio)} are input only.`);
LC.value="";
LC.focus();
ok = false;
return;
}
for (j=i+1; j<nList.length; j++) {
let n2 = nList[j].name.substring(0,2); // field name /L./
if (n2.search(/^L[0-4]/) == 0) { // pin fields
let m = nList[j].name.substring(2,3); // bus number (0-Z)
let t2 = parseInt(gN("LT"+m).value, 10);
if (isVir(t2)) continue;
if (nList[j].value!="" && nList[i].value==nList[j].value) {
alert(`Pin conflict between ${LC.name}/${nList[j].name}!`);
nList[j].value="";
nList[j].focus();
ok = false;
return;
if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3" || nm=="L4")
if (LC.value!="" && LC.value!="-1") {
let p = d.rsvd.concat(d.um_p); // used pin array
d.Sf.querySelectorAll("select.pin").forEach((e)=>{if(e.value>-1)p.push(parseInt(e.value));}) // buttons, IR & relay
if (p.some((e)=>e==parseInt(LC.value))) {
alert(`Sorry, pins ${JSON.stringify(p)} can't be used.`);
LC.value="";
LC.focus();
ok = false;
return;
} else if (d.ro_gpio.some((e)=>e==parseInt(LC.value))) {
alert(`Sorry, pins ${JSON.stringify(d.ro_gpio)} are input only.`);
LC.value="";
LC.focus();
ok = false;
return;
}
for (j=i+1; j<nList.length; j++) {
let n2 = nList[j].name.substring(0,2);
if (n2=="L0" || n2=="L1" || n2=="L2" || n2=="L3" || n2=="L4") {
if (n2.substring(0,1)==="L") {
var m = nList[j].name.substring(2);
var t2 = parseInt(d.Sf["LT"+m].value, 10);
if (t2>=80) continue;
}
if (nList[j].value!="" && nList[i].value==nList[j].value) {
alert(`Pin conflict between ${LC.name}/${nList[j].name}!`);
nList[j].value="";
nList[j].focus();
ok = false;
return;
}
}
}
@@ -112,15 +111,11 @@
d.Sf.data.value = '';
e.preventDefault();
if (!pinsOK()) {e.stopPropagation();return false;} // Prevent form submission and contact with server
if (bquot > 200) {var msg = "Too many LEDs! Can't handle that!"; alert(msg); e.stopPropagation(); return false;}
else {
if (bquot > 80) {var msg = "Memory usage is high, reboot recommended!\n\rSet transitions to 0 to save memory.";
if (bquot > 100) msg += "\n\rToo many LEDs for me to handle properly!"; if (maxM < 10000) msg += "\n\rConsider using an ESP32."; alert(msg);}
if (!d.Sf.ABL.checked || d.Sf.PPL.checked) d.Sf.MA.value = 0; // submit 0 as ABL (PPL will handle it)
if (d.Sf.checkValidity()) {
d.Sf.querySelectorAll("#mLC select[name^=LT]").forEach((s)=>{s.disabled=false;}); // just in case
d.Sf.submit(); //https://stackoverflow.com/q/37323914
}
if (bquot > 100) {var msg = "Too many LEDs for me to handle!"; if (maxM < 10000) msg += "\n\rConsider using an ESP32."; alert(msg);}
if (!d.Sf.ABL.checked || d.Sf.PPL.checked) d.Sf.MA.value = 0; // submit 0 as ABL (PPL will handle it)
if (d.Sf.checkValidity()) {
d.Sf.querySelectorAll("#mLC select[name^=LT]").forEach((s)=>{s.disabled=false;}); // just in case
d.Sf.submit(); //https://stackoverflow.com/q/37323914
}
}
function enABL()
@@ -198,27 +193,21 @@
//returns mem usage
function getMem(t, n) {
if (isAna(t)) return 5; // analog
let len = parseInt(d.Sf["LC"+n].value);
len += parseInt(d.Sf["SL"+n].value); // skipped LEDs are allocated too
let len = parseInt(d.getElementsByName("LC"+n)[0].value);
len += parseInt(d.getElementsByName("SL"+n)[0].value); // skipped LEDs are allocated too
let dbl = 0;
let pbfr = len * 8; // pixel buffers: global buffer + segment buffer (at least one segment buffer is required)
let ch = 3*hasRGB(t) + hasW(t) + hasCCT(t);
let mul = 1;
if (isDig(t)) {
if (is16b(t)) len *= 2; // 16 bit LEDs
if (is8266() && d.Sf["L0"+n].value == 3) { //8266 DMA uses 5x the mem
if (maxM < 10000 && d.getElementsByName("L0"+n)[0].value == 3) { //8266 DMA uses 5x the mem
mul = 5;
}
let parallelI2S = d.Sf.PR.checked && (is32() || isS2() || isS3()) && !isD2P(t);
if (isC3() || (isS3() && !parallelI2S)) {
mul = 2; // ESP32 RMT uses double buffer
} else if ((is32() || isS2() || isS3()) && toNum(n) > (parallelI2S ? 7 : 0)) {
mul = 2; // ESP32 RMT uses double buffer
} else if ((parallelI2S && toNum(n) < 8) || (n == 0 && is32())) { // I2S uses extra DMA buffer
dbl = len * ch * 3; // DMA buffer for parallel I2S (TODO: ony the bus with largst LED count should be used)
if (maxM >= 10000) { //ESP32 RMT uses double buffer?
mul = 2;
}
}
return len * ch * mul + dbl + pbfr;
return len * ch * mul + dbl;
}
function UI(change=false)
@@ -248,15 +237,12 @@
case 'V': // virtual/non-GPIO based
p0d = "Config:"
break;
case 'H': // HUB75
p0d = "Panel size (width x height), Panel count:"
break;
}
gId("p0d"+n).innerText = p0d;
gId("p1d"+n).innerText = p1d;
gId("off"+n).innerText = off;
// secondary pins show/hide (type string length is equivalent to number of pins used; except for network and on/off)
let pins = Math.max(gT(t).t.length,1) + 3*isNet(t) + 2*isHub75(t); // fixes network pins to 4
let pins = Math.max(gT(t).t.length,1) + 3*isNet(t); // fixes network pins to 4
for (let p=1; p<5; p++) {
var LK = d.Sf["L"+p+n];
if (!LK) continue;
@@ -272,7 +258,7 @@
LTs.forEach((s,i)=>{
if (i < LTs.length-1) s.disabled = true; // prevent changing type (as we can't update options)
// is the field a LED type?
var n = s.name.substring(2,3); // bus number (0-Z)
var n = s.name.substring(2);
var t = parseInt(s.value);
memu += getMem(t, n); // calc memory
dC += (isDig(t) && !isD2P(t));
@@ -286,13 +272,13 @@
}
gId("rf"+n).onclick = mustR(t) ? (()=>{return false}) : (()=>{}); // prevent change change of "Refresh" checkmark when mandatory
gRGBW |= hasW(t); // RGBW checkbox
gId("co"+n).style.display = (isVir(t) || isAna(t) || isHub75(t)) ? "none":"inline"; // hide color order for PWM
gId("co"+n).style.display = (isVir(t) || isAna(t)) ? "none":"inline"; // hide color order for PWM
gId("dig"+n+"w").style.display = (isDig(t) && hasW(t)) ? "inline":"none"; // show swap channels dropdown
gId("dig"+n+"w").querySelector("[data-opt=CCT]").disabled = !hasCCT(t); // disable WW/CW swapping
if (!(isDig(t) && hasW(t))) d.Sf["WO"+n].value = 0; // reset swapping
gId("dig"+n+"c").style.display = (isAna(t) || isHub75(t)) ? "none":"inline"; // hide count for analog
gId("dig"+n+"c").style.display = (isAna(t)) ? "none":"inline"; // hide count for analog
gId("dig"+n+"r").style.display = (isVir(t)) ? "none":"inline"; // hide reversed for virtual
gId("dig"+n+"s").style.display = (isVir(t) || isAna(t) || isHub75(t)) ? "none":"inline"; // hide skip 1st for virtual & analog
gId("dig"+n+"s").style.display = (isVir(t) || isAna(t)) ? "none":"inline"; // hide skip 1st for virtual & analog
gId("dig"+n+"f").style.display = (isDig(t) || (isPWM(t) && maxL>2048)) ? "inline":"none"; // hide refresh (PWM hijacks reffresh for dithering on ESP32)
gId("dig"+n+"a").style.display = (hasW(t)) ? "inline":"none"; // auto calculate white
gId("dig"+n+"l").style.display = (isD2P(t) || isPWM(t)) ? "inline":"none"; // bus clock speed / PWM speed (relative) (not On/Off)
@@ -311,8 +297,8 @@
let sameType = 0;
var nList = d.Sf.querySelectorAll("#mLC input[name^=L]");
nList.forEach((LC,i)=>{
let nm = LC.name.substring(0,2); // field name : /L./
let n = LC.name.substring(2,3); // bus number (0-Z)
let nm = LC.name.substring(0,2); // field name
let n = LC.name.substring(2); // bus number
let t = parseInt(d.Sf["LT"+n].value); // LED type SELECT
if (isDig(t)) {
if (sameType == 0) sameType = t; // first bus type
@@ -321,7 +307,7 @@
// do we have a led count field
if (nm=="LC") {
let c = parseInt(LC.value,10); //get LED count
if (!customStarts || !startsDirty[toNum(n)]) gId("ls"+n).value = sLC; //update start value
if (!customStarts || !startsDirty[n]) gId("ls"+n).value = sLC; //update start value
gId("ls"+n).disabled = !customStarts; //enable/disable field editing
if (c) {
let s = parseInt(gId("ls"+n).value); //start value
@@ -339,16 +325,10 @@
}
// do we have led pins for digital leds
if (nm=="L0" || nm=="L1") {
if (!isHub75(t)) {
d.Sf["LC"+n].max = maxPB; // update max led count value
}
else {
d.Sf["LC"+n].min = undefined;
d.Sf["LC"+n].max = undefined;
}
d.Sf["LC"+n].max = maxPB; // update max led count value
}
// ignore IP address (stored in pins for virtual busses)
if (nm.search(/^L[0-3]/) == 0) { // pin fields
if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3") {
if (isVir(t)) {
LC.max = 255;
LC.min = 0;
@@ -359,39 +339,27 @@
LC.min = -1;
}
}
if (isHub75(t) && (nm=="L0" || nm=="L1")) {
// Matrix width and height
LC.max = 128;
LC.min = 16;
LC.style.color="#fff";
return; // do not check conflicts
}
else if (isHub75(t) && nm=="L2") {
// Chain length aka Panel Count
LC.max = 4;
LC.min = 1;
LC.style.color="#fff";
return; // do not check conflicts
}
// check for pin conflicts & color fields
if (nm.search(/^L[0-4]/) == 0) // pin fields
if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3" || nm=="L4")
if (LC.value!="" && LC.value!="-1") {
let p = d.rsvd.concat(d.um_p); // used pin array
d.Sf.querySelectorAll("select.pin").forEach((e)=>{if(e.value>-1)p.push(parseInt(e.value));}) // buttons, IR & relay
for (j=0; j<nList.length; j++) {
if (i==j) continue;
let n2 = nList[j].name.substring(0,2); // field name : /L./
if (n2.search(/^L[0-4]/) == 0) { // pin fields
let m = nList[j].name.substring(2,3); // bus number (0-Z)
let t2 = parseInt(gN("LT"+m).value, 10);
if (isVir(t2)) continue;
let n2 = nList[j].name.substring(0,2);
if (n2=="L0" || n2=="L1" || n2=="L2" || n2=="L3" || n2=="L4") {
if (n2.substring(0,1)==="L") {
let m = nList[j].name.substring(2);
let t2 = parseInt(d.Sf["LT"+m].value, 10);
if (isVir(t2)) continue;
}
if (nList[j].value!="" && nList[j].value!="-1") p.push(parseInt(nList[j].value,10)); // add current pin
}
}
// now check for conflicts
if (p.some((e)=>e==parseInt(LC.value))) LC.style.color = "red";
else LC.style.color = d.ro_gpio.some((e)=>e==parseInt(LC.value)) ? "orange" : "#fff";
} else LC.style.color = "#fff";
}
});
if (is32() || isS2() || isS3()) {
if (maxLC > 600 || dC < 2 || sameType <= 0) {
@@ -413,7 +381,7 @@
gId('dbar').style.background = `linear-gradient(90deg, ${bquot > 60 ? (bquot > 90 ? "red":"orange"):"#ccc"} 0 ${bquot}%, #444 ${bquot}% 100%)`;
gId('ledwarning').style.display = (maxLC > Math.min(maxPB,800) || bquot > 80) ? 'inline':'none';
gId('ledwarning').style.color = (maxLC > Math.max(maxPB,800) || bquot > 100) ? 'red':'orange';
gId('wreason').innerHTML = (bquot > 80) ? "80% of max LED memory" +(bquot>100 ? ` (<b>WARNING: using over ${maxM}B!</b>)` : "") : "800 LEDs per output";
gId('wreason').innerHTML = (bquot > 80) ? "80% of max. LED memory" +(bquot>100 ? ` (<b>ERROR: Using over ${maxM}B!</b>)` : "") : "800 LEDs per output";
// calculate power
gId('ampwarning').style.display = (parseInt(d.Sf.MA.value,10) > 7200) ? 'inline':'none';
var val = Math.ceil((100 + busMA)/500)/2;

View File

@@ -4,7 +4,7 @@
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<meta charset="utf-8">
<title>Misc Settings</title>
<script src="common.js" type="text/javascript"></script>
<script src="common.js" async type="text/javascript"></script>
<script>
function U() { window.open(getURL("/update"),"_self"); }
function checkNum(o) {
@@ -48,7 +48,7 @@
To enable OTA, for security reasons you need to also enter the correct password!<br>
The password should be changed when OTA is enabled.<br>
<b>Disable OTA when not in use, otherwise an attacker can reflash device software!</b><br>
<i>Settings on this page are only changeable if OTA lock is disabled!</i><br>
<i>Settings on this page are only changable if OTA lock is disabled!</i><br>
Deny access to WiFi settings if locked: <input type="checkbox" name="OW"><br><br>
Factory reset: <input type="checkbox" name="RS"><br>
All settings and presets will be erased.<br><br>

View File

@@ -4,7 +4,7 @@
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<meta charset="utf-8">
<title>Sync Settings</title>
<script src="common.js" type="text/javascript"></script>
<script src="common.js" async type="text/javascript"></script>
<script>
function adj(){if (d.Sf.DI.value == 6454) {if (d.Sf.EU.value == 1) d.Sf.EU.value = 0;}
else if (d.Sf.DI.value == 5568) {if (d.Sf.DA.value == 0) d.Sf.DA.value = 1; if (d.Sf.EU.value == 0) d.Sf.EU.value = 1;} }
@@ -34,12 +34,7 @@
function SetVal(){switch(parseInt(d.Sf.EP.value)){case 5568: d.Sf.DI.value = 5568; break; case 6454: d.Sf.DI.value = 6454; break; case 4048: d.Sf.DI.value = 4048; break; }; SP();FC();}
function S(){
getLoc();
loadJS(getURL('/settings/s.js?p=4'), false, ()=>{
d.um_p = [];
d.rsvd = [];
d.ro_gpio = [];
d.max_gpio = 50;
}, ()=>{SetVal();pinDropdowns();}); // If we set async false, file is loaded and executed, then next statement is processed
loadJS(getURL('/settings/s.js?p=4'), false, undefined, ()=>{SetVal();}); // If we set async false, file is loaded and executed, then next statement is processed
if (loc) d.Sf.action = getURL('/settings/sync');
}
function getURL(path) {
@@ -47,88 +42,6 @@
}
function hideDMXInput(){gId("dmxInput").style.display="none";}
function hideNoDMXInput(){gId("dmxInputOff").style.display="none";}
function pinDropdowns() {
let fields = ["IDMR","IDMT","IDME"]; // DMX input pins
for (let i of d.Sf.elements) {
if (i.type === "number" && fields.includes(i.name)) { //select all pin select elements
let v = parseInt(i.value);
let sel = addDropdown(i.name);
for (var j = -1; j < d.max_gpio; j++) {
if (d.rsvd.includes(j)) continue;
let foundPin = d.um_p.indexOf(j);
let txt = (j === -1) ? "unused" : `${j}`;
if (foundPin >= 0 && j !== v) txt += ` used`; // already reserved pin
if (d.ro_gpio.includes(j)) txt += " (R/O)";
let opt = addOption(sel, txt, j);
if (j === v) opt.selected = true; // this is "our" pin
else if (d.um_p.includes(j)) opt.disabled = true; // someone else's pin
}
}
}
// update select options
d.Sf.querySelectorAll("select.pin").forEach((e)=>{pinUpd(e);});
}
function pinUpd(e) {
// update changed select options across DMX pins
let oldV = parseInt(e.dataset.val);
e.dataset.val = e.value;
let txt = e.name;
let selects = d.Sf.querySelectorAll("select.pin");
for (let sel of selects) {
if (sel == e) continue;
Array.from(sel.options).forEach((i)=>{
if (!(i.value==oldV || i.value==e.value)) return;
if (i.value == -1) {
i.text = "unused";
return;
}
i.text = i.value;
if (i.value==oldV) {
i.disabled = false;
}
if (i.value==e.value) {
i.disabled = true;
i.text += ` ${txt}`;
}
if (d.ro_gpio.includes(parseInt(i.value))) i.text += " (R/O)";
});
}
}
function addDropdown(field) {
let sel = cE('select');
sel.classList.add("pin");
let inp = d.getElementsByName(field)[0];
if (inp && inp.tagName === "INPUT" && (inp.type === "text" || inp.type === "number")) {
let v = inp.value;
let n = inp.name;
// copy the existing input element's attributes to the new select element
for (var i = 0; i < inp.attributes.length; ++ i) {
var att = inp.attributes[i];
// type and value don't apply, so skip them
if (att.name != 'type' && att.name != 'value' && att.name != 'class' && att.name != 'style') {
sel.setAttribute(att.name, att.value);
}
}
sel.setAttribute("data-val", v);
sel.setAttribute("onchange", "pinUpd(this)");
// finally, replace the old input element with the new select element
inp.parentElement.replaceChild(sel, inp);
return sel;
}
return null;
}
function addOption(sel,txt,val) {
if (sel===null) return; // select object missing
let opt = cE("option");
opt.value = val;
opt.text = txt;
sel.appendChild(opt);
for (let i=0; i<sel.childNodes.length; i++) {
let c = sel.childNodes[i];
if (c.value == sel.dataset.val) sel.selectedIndex = i;
}
return opt;
}
</script>
<style>@import url("style.css");</style>
</head>

View File

@@ -4,7 +4,7 @@
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<meta charset="utf-8">
<title>Time Settings</title>
<script src="common.js" type="text/javascript"></script>
<script src="common.js" async type="text/javascript"></script>
<script>
var el=false;
var ms=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];

View File

@@ -4,7 +4,7 @@
<meta charset="utf-8">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<title>UI Settings</title>
<script src="common.js" type="text/javascript"></script>
<script src="common.js" async type="text/javascript"></script>
<script>
var initial_ds, initial_st, initial_su, oldUrl;
var sett = null;

View File

@@ -4,7 +4,7 @@
<meta charset="utf-8">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<title>Usermod Settings</title>
<script src="common.js" type="text/javascript"></script>
<script src="common.js" async type="text/javascript"></script>
<script>
var umCfg = {};
var pins = [], pinO = [], owner;

View File

@@ -4,7 +4,7 @@
<meta charset="utf-8">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<title>WiFi Settings</title>
<script src="common.js" type="text/javascript"></script>
<script src="common.js" async type="text/javascript"></script>
<script>
var scanLoops = 0, preScanSSID = "";
var maxNetworks = 3;

View File

@@ -3,7 +3,7 @@
<head>
<meta content='width=device-width' name='viewport'>
<title>WLED Update</title>
<script src="common.js" type="text/javascript"></script>
<script src="common.js" async type="text/javascript"></script>
<script>
function B() { window.history.back(); }
var cnfr = false;

View File

@@ -30,19 +30,11 @@ void handleDDPPacket(e131_packet_t* p) {
uint32_t start = htonl(p->channelOffset) / ddpChannelsPerLed;
start += DMXAddress / ddpChannelsPerLed;
uint16_t dataLen = htons(p->dataLen);
unsigned stop = start + dataLen / ddpChannelsPerLed;
unsigned stop = start + htons(p->dataLen) / ddpChannelsPerLed;
uint8_t* data = p->data;
unsigned c = 0;
if (p->flags & DDP_TIMECODE_FLAG) c = 4; //packet has timecode flag, we do not support it, but data starts 4 bytes later
unsigned numLeds = stop - start; // stop >= start is guaranteed
unsigned maxDataIndex = c + numLeds * ddpChannelsPerLed; // validate bounds before accessing data array
if (maxDataIndex > dataLen) {
DEBUG_PRINTLN(F("DDP packet data bounds exceeded, rejecting."));
return;
}
if (realtimeMode != REALTIME_MODE_DDP) ddpSeenPush = false; // just starting, no push yet
realtimeLock(realtimeTimeoutMs, REALTIME_MODE_DDP);

View File

@@ -27,7 +27,6 @@ void IRAM_ATTR touchButtonISR();
bool backupConfig();
bool restoreConfig();
bool verifyConfig();
bool configBackupExists();
void resetConfig();
bool deserializeConfig(JsonObject doc, bool fromFS = false);
bool deserializeConfigFromFS();
@@ -104,7 +103,6 @@ inline bool readObjectFromFile(const String &file, const char* key, JsonDocument
bool copyFile(const char* src_path, const char* dst_path);
bool backupFile(const char* filename);
bool restoreFile(const char* filename);
bool checkBackupExists(const char* filename);
bool validateJsonFile(const char* filename);
void dumpFilesToSerial();
@@ -436,44 +434,35 @@ 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
// memory allocation wrappers (util.cpp)
// PSRAM allocation wrappers
#if !defined(ESP8266) && !defined(CONFIG_IDF_TARGET_ESP32C3)
extern "C" {
// prefer DRAM in d_xalloc functions, PSRAM as fallback
void *d_malloc(size_t);
void *d_calloc(size_t, size_t);
void *d_realloc_malloc(void *ptr, size_t size);
#ifndef ESP8266
inline void d_free(void *ptr) { heap_caps_free(ptr); }
#else
inline void d_free(void *ptr) { free(ptr); }
#endif
#if defined(BOARD_HAS_PSRAM)
// prefer PSRAM in p_xalloc functions, DRAM as fallback
void *p_malloc(size_t);
void *p_calloc(size_t, size_t);
void *p_realloc_malloc(void *ptr, size_t size);
void *p_malloc(size_t); // prefer PSRAM over DRAM
void *p_calloc(size_t, size_t); // prefer PSRAM over DRAM
void *p_realloc(void *, size_t); // prefer PSRAM over DRAM
void *p_realloc_malloc(void *ptr, size_t size); // realloc with malloc fallback, prefer PSRAM over DRAM
inline void p_free(void *ptr) { heap_caps_free(ptr); }
#else
#define p_malloc d_malloc
#define p_calloc d_calloc
#define p_realloc_malloc d_realloc_malloc
#define p_free d_free
#endif
void *d_malloc(size_t); // prefer DRAM over PSRAM
void *d_calloc(size_t, size_t); // prefer DRAM over PSRAM
void *d_realloc(void *, size_t); // prefer DRAM over PSRAM
void *d_realloc_malloc(void *ptr, size_t size); // realloc with malloc fallback, prefer DRAM over PSRAM
inline void d_free(void *ptr) { heap_caps_free(ptr); }
}
#ifndef ESP8266
inline size_t getFreeHeapSize() { return heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); } // returns free heap (ESP.getFreeHeap() can include other memory types)
inline size_t getContiguousFreeHeap() { return heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); } // returns largest contiguous free block
#else
inline size_t getFreeHeapSize() { return ESP.getFreeHeap(); } // returns free heap
inline size_t getContiguousFreeHeap() { return ESP.getMaxFreeBlockSize(); } // returns largest contiguous free block
extern "C" {
void *realloc_malloc(void *ptr, size_t size);
}
#define p_malloc malloc
#define p_calloc calloc
#define p_realloc realloc
#define p_realloc_malloc realloc_malloc
#define p_free free
#define d_malloc malloc
#define d_calloc calloc
#define d_realloc realloc
#define d_realloc_malloc realloc_malloc
#define d_free free
#endif
#define BFRALLOC_NOBYTEACCESS (1 << 0) // ESP32 has 32bit accessible DRAM (usually ~50kB free) that must not be byte-accessed
#define BFRALLOC_PREFER_DRAM (1 << 1) // prefer DRAM over PSRAM
#define BFRALLOC_ENFORCE_DRAM (1 << 2) // use DRAM only, no PSRAM
#define BFRALLOC_PREFER_PSRAM (1 << 3) // prefer PSRAM over DRAM
#define BFRALLOC_ENFORCE_PSRAM (1 << 4) // use PSRAM if available, otherwise uses DRAM
#define BFRALLOC_CLEAR (1 << 5) // clear allocated buffer after allocation
void *allocate_buffer(size_t size, uint32_t type);
void handleBootLoop(); // detect and handle bootloops
#ifndef ESP8266

View File

@@ -422,8 +422,8 @@ bool handleFileRead(AsyncWebServerRequest* request, String path){
DEBUGFS_PRINT(F("WS FileRead: ")); DEBUGFS_PRINTLN(path);
if(path.endsWith("/")) path += "index.htm";
if(path.indexOf(F("sec")) > -1) return false;
#ifdef BOARD_HAS_PSRAM
if (path.endsWith(FPSTR(getPresetsFileName()))) {
#ifdef ARDUINO_ARCH_ESP32
if (psramSafe && psramFound() && path.endsWith(FPSTR(getPresetsFileName()))) {
size_t psize;
const uint8_t *presets = getPresetCache(psize);
if (presets) {
@@ -557,12 +557,6 @@ bool restoreFile(const char* filename) {
return false;
}
bool checkBackupExists(const char* filename) {
char backupname[32];
snprintf_P(backupname, sizeof(backupname), s_backup_fmt, filename + 1); // skip leading '/' in filename
return WLED_FS.exists(backupname);
}
bool validateJsonFile(const char* filename) {
if (!WLED_FS.exists(filename)) return false;
File file = WLED_FS.open(filename, "r");

View File

@@ -1,5 +1,7 @@
#include "wled.h"
#include "palettes.h"
#define JSON_PATH_STATE 1
#define JSON_PATH_INFO 2
#define JSON_PATH_STATE_INFO 3
@@ -693,7 +695,6 @@ void serializeInfo(JsonObject root)
root[F("vid")] = VERSION;
root[F("cn")] = F(WLED_CODENAME);
root[F("release")] = releaseString;
root[F("repo")] = repoString;
JsonObject leds = root.createNestedObject(F("leds"));
leds[F("count")] = strip.getLengthTotal();
@@ -769,8 +770,7 @@ void serializeInfo(JsonObject root)
root[F("fxcount")] = strip.getModeCount();
root[F("palcount")] = getPaletteCount();
root[F("cpalcount")] = customPalettes.size(); // number of custom palettes
root[F("cpalmax")] = WLED_MAX_CUSTOM_PALETTES; // maximum number of custom palettes
root[F("cpalcount")] = customPalettes.size(); //number of custom palettes
JsonArray ledmaps = root.createNestedArray(F("maps"));
for (size_t i=0; i<WLED_MAX_LEDMAPS; i++) {
@@ -812,7 +812,7 @@ void serializeInfo(JsonObject root)
root[F("clock")] = ESP.getCpuFreqMHz();
root[F("flash")] = (ESP.getFlashChipSize()/1024)/1024;
#ifdef WLED_DEBUG
root[F("maxalloc")] = getContiguousFreeHeap();
root[F("maxalloc")] = ESP.getMaxAllocHeap();
root[F("resetReason0")] = (int)rtc_get_reset_reason(0);
root[F("resetReason1")] = (int)rtc_get_reset_reason(1);
#endif
@@ -823,15 +823,15 @@ void serializeInfo(JsonObject root)
root[F("clock")] = ESP.getCpuFreqMHz();
root[F("flash")] = (ESP.getFlashChipSize()/1024)/1024;
#ifdef WLED_DEBUG
root[F("maxalloc")] = getContiguousFreeHeap();
root[F("maxalloc")] = ESP.getMaxFreeBlockSize();
root[F("resetReason")] = (int)ESP.getResetInfoPtr()->reason;
#endif
root[F("lwip")] = LWIP_VERSION_MAJOR;
#endif
root[F("freeheap")] = getFreeHeapSize();
#if defined(BOARD_HAS_PSRAM)
root[F("psram")] = ESP.getFreePsram();
root[F("freeheap")] = ESP.getFreeHeap();
#if defined(ARDUINO_ARCH_ESP32)
if (psramFound()) root[F("psram")] = ESP.getFreePsram();
#endif
root[F("uptime")] = millis()/1000 + rolloverMillis*4294967;
@@ -934,7 +934,7 @@ void serializePalettes(JsonObject root, int page)
#endif
int customPalettesCount = customPalettes.size();
int palettesCount = getPaletteCount() - customPalettesCount; // palettesCount is number of palettes, not palette index
int palettesCount = getPaletteCount() - customPalettesCount;
int maxPage = (palettesCount + customPalettesCount -1) / itemPerPage;
if (page > maxPage) page = maxPage;
@@ -946,8 +946,8 @@ void serializePalettes(JsonObject root, int page)
root[F("m")] = maxPage; // inform caller how many pages there are
JsonObject palettes = root.createNestedObject("p");
for (int i = start; i <= end; i++) {
JsonArray curPalette = palettes.createNestedArray(String(i<=palettesCount ? i : 255 - (i - (palettesCount + 1))));
for (int i = start; i < end; i++) {
JsonArray curPalette = palettes.createNestedArray(String(i>=palettesCount ? 255 - i + palettesCount : i));
switch (i) {
case 0: //default palette
setPaletteColors(curPalette, PartyColors_p);
@@ -976,8 +976,8 @@ void serializePalettes(JsonObject root, int page)
curPalette.add("c1");
break;
default:
if (i > palettesCount)
setPaletteColors(curPalette, customPalettes[i - (palettesCount + 1)]);
if (i >= palettesCount)
setPaletteColors(curPalette, customPalettes[i - palettesCount]);
else if (i < 13) // palette 6 - 12, fastled palettes
setPaletteColors(curPalette, *fastledPalettes[i-6]);
else {

5
wled00/palettes.cpp → wled00/palettes.h Normal file → Executable file
View File

@@ -1,4 +1,5 @@
#include "wled.h"
#ifndef PalettesWLED_h
#define PalettesWLED_h
/*
* WLED Color palettes
@@ -767,3 +768,5 @@ const uint8_t* const gGradientPalettes[] PROGMEM = {
candy2_gp, //70-57 Candy2
trafficlight_gp //71-58 Traffic Light
};
#endif

View File

@@ -139,12 +139,6 @@ bool PinManager::allocateMultiplePins(const managed_pin_type * mptArray, byte ar
return true;
}
bool PinManager::allocateMultiplePins(const int8_t * mptArray, byte arrayElementCount, PinOwner tag, boolean output) {
PinManagerPinType pins[arrayElementCount];
for (int i=0; i<arrayElementCount; i++) pins[i] = {mptArray[i], output};
return allocateMultiplePins(pins, arrayElementCount, 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

View File

@@ -40,7 +40,6 @@ enum struct PinOwner : uint8_t {
HW_I2C = 0x8B, // 'I2C' == hardware I2C pins (4&5 on ESP8266, 21&22 on ESP32)
HW_SPI = 0x8C, // 'SPI' == hardware (V)SPI pins (13,14&15 on ESP8266, 5,18&23 on ESP32)
DMX_INPUT = 0x8D, // 'DMX_INPUT' == DMX input via serial
HUB75 = 0x8E, // 'Hub75' == Hub75 driver
// Use UserMod IDs from const.h here
UM_Unspecified = USERMOD_ID_UNSPECIFIED, // 0x01
UM_Example = USERMOD_ID_EXAMPLE, // 0x02 // Usermod "usermod_v2_example.h"
@@ -87,7 +86,6 @@ namespace PinManager {
// using more than one pin, such as I2C, SPI, rotary encoders,
// ethernet, etc..
bool allocateMultiplePins(const managed_pin_type * mptArray, byte arrayElementCount, PinOwner tag );
bool allocateMultiplePins(const int8_t * mptArray, byte arrayElementCount, PinOwner tag, boolean output);
[[deprecated("Replaced by three-parameter allocatePin(gpio, output, ownerTag), for improved debugging")]]
inline bool allocatePin(byte gpio, bool output = true) { return allocatePin(gpio, output, PinOwner::None); }
@@ -98,7 +96,7 @@ namespace PinManager {
bool isPinAllocated(byte gpio, PinOwner tag = PinOwner::None);
// will return false for reserved pins
bool isPinOk(byte gpio, bool output = true);
bool isReadOnlyPin(byte gpio);
PinOwner getPinOwner(byte gpio);

View File

@@ -140,7 +140,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
unsigned colorOrder, type, skip, awmode, channelSwap, maPerLed;
unsigned length, start, maMax;
uint8_t pins[OUTPUT_MAX_PINS] = {255, 255, 255, 255, 255};
uint8_t pins[5] = {255, 255, 255, 255, 255};
String text;
// this will set global ABL max current used when per-port ABL is not used

View File

@@ -230,7 +230,7 @@ uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLe
} else return 0;
}
if (src == JSON_palette_names && mode > 255-customPalettes.size()) {
if (src == JSON_palette_names && mode > (GRADIENT_PALETTE_COUNT + 13)) {
snprintf_P(dest, maxLen, PSTR("~ Custom %d ~"), 255-mode);
dest[maxLen-1] = '\0';
return strlen(dest);
@@ -629,187 +629,93 @@ int32_t hw_random(int32_t lowerlimit, int32_t upperlimit) {
return hw_random(diff) + lowerlimit;
}
// PSRAM compile time checks to provide info for misconfigured env
#if defined(BOARD_HAS_PSRAM)
#if defined(IDF_TARGET_ESP32C3) || defined(ESP8266)
#error "ESP32-C3 and ESP8266 with PSRAM is not supported, please remove BOARD_HAS_PSRAM definition"
#else
// BOARD_HAS_PSRAM also means that compiler flag "-mfix-esp32-psram-cache-issue" has to be used
#warning "BOARD_HAS_PSRAM defined, make sure to use -mfix-esp32-psram-cache-issue to prevent issues on rev.1 ESP32 boards \
see https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/external-ram.html#esp32-rev-v1-0"
#endif
#else
#if !defined(IDF_TARGET_ESP32C3) && !defined(ESP8266)
#pragma message("BOARD_HAS_PSRAM not defined, not using PSRAM.")
#endif
#endif
// memory allocation functions with minimum free heap size check
#ifdef ESP8266
static void *validateFreeHeap(void *buffer) {
// make sure there is enough free heap left if buffer was allocated in DRAM region, free it if not
if (getContiguousFreeHeap() < MIN_HEAP_SIZE) {
free(buffer);
return nullptr;
}
return buffer;
}
void *d_malloc(size_t size) {
// note: using "if (getContiguousFreeHeap() > MIN_HEAP_SIZE + size)" did perform worse in tests with regards to keeping heap healthy and UI working
void *buffer = malloc(size);
return validateFreeHeap(buffer);
}
void *d_calloc(size_t count, size_t size) {
void *buffer = calloc(count, size);
return validateFreeHeap(buffer);
}
// realloc with malloc fallback, note: on ESPS8266 there is no safe way to ensure MIN_HEAP_SIZE during realloc()s, free buffer and allocate new one
void *d_realloc_malloc(void *ptr, size_t size) {
//void *buffer = realloc(ptr, size);
//buffer = validateFreeHeap(buffer);
//if (buffer) return buffer; // realloc successful
//d_free(ptr); // free old buffer if realloc failed (or min heap was exceeded)
//return d_malloc(size); // fallback to malloc
free(ptr);
return d_malloc(size);
}
#else
static void *validateFreeHeap(void *buffer) {
// make sure there is enough free heap left if buffer was allocated in DRAM region, free it if not
// TODO: between allocate and free, heap can run low (async web access), only IDF V5 allows for a pre-allocation-check of all free blocks
if ((uintptr_t)buffer > SOC_DRAM_LOW && (uintptr_t)buffer < SOC_DRAM_HIGH && getContiguousFreeHeap() < MIN_HEAP_SIZE) {
free(buffer);
return nullptr;
}
return buffer;
}
void *d_malloc(size_t size) {
void *buffer;
#if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3)
// the newer ESP32 variants have byte-accessible fast RTC memory that can be used as heap, access speed is on-par with DRAM
// the system does prefer normal DRAM until full, since free RTC memory is ~7.5k only, its below the minimum heap threshold and needs to be allocated explicitly
// use RTC RAM for small allocations to improve fragmentation or if DRAM is running low
if (size < 256 || getContiguousFreeHeap() < 2*MIN_HEAP_SIZE + size)
buffer = heap_caps_malloc_prefer(size, 2, MALLOC_CAP_RTCRAM, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
else
#endif
buffer = heap_caps_malloc(size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); // allocate in any available heap memory
buffer = validateFreeHeap(buffer); // make sure there is enough free heap left
#ifdef BOARD_HAS_PSRAM
if (!buffer)
return heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); // DRAM failed, use PSRAM if available
#endif
return buffer;
}
void *d_calloc(size_t count, size_t size) {
void *buffer = d_malloc(count * size);
if (buffer) memset(buffer, 0, count * size); // clear allocated buffer
return buffer;
}
// realloc with malloc fallback, original buffer is freed if realloc fails but not copied!
void *d_realloc_malloc(void *ptr, size_t size) {
void *buffer = heap_caps_realloc(ptr, size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
buffer = validateFreeHeap(buffer);
if (buffer) return buffer; // realloc successful
d_free(ptr); // free old buffer if realloc failed (or min heap was exceeded)
return d_malloc(size); // fallback to malloc
}
#ifdef BOARD_HAS_PSRAM
// p_xalloc: prefer PSRAM, use DRAM as fallback
#if !defined(ESP8266) && !defined(CONFIG_IDF_TARGET_ESP32C3) // ESP8266 does not support PSRAM, ESP32-C3 does not have PSRAM
// p_x prefer PSRAM, d_x prefer DRAM
void *p_malloc(size_t size) {
void *buffer = heap_caps_malloc_prefer(size, 2, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
return validateFreeHeap(buffer);
int caps1 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
int caps2 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
if (psramSafe) {
if (heap_caps_get_free_size(caps2) > 3*MIN_HEAP_SIZE && size < 512) std::swap(caps1, caps2); // use DRAM for small alloactions & when heap is plenty
return heap_caps_malloc_prefer(size, 2, caps1, caps2); // otherwise prefer PSRAM if it exists
}
return heap_caps_malloc(size, caps2);
}
void *p_calloc(size_t count, size_t size) {
void *buffer = p_malloc(count * size);
if (buffer) memset(buffer, 0, count * size); // clear allocated buffer
return buffer;
void *p_realloc(void *ptr, size_t size) {
int caps1 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
int caps2 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
if (psramSafe) {
if (heap_caps_get_free_size(caps2) > 3*MIN_HEAP_SIZE && size < 512) std::swap(caps1, caps2); // use DRAM for small alloactions & when heap is plenty
return heap_caps_realloc_prefer(ptr, size, 2, caps1, caps2); // otherwise prefer PSRAM if it exists
}
return heap_caps_realloc(ptr, size, caps2);
}
// realloc with malloc fallback, original buffer is freed if realloc fails but not copied!
void *p_realloc_malloc(void *ptr, size_t size) {
void *buffer = heap_caps_realloc(ptr, size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (buffer) return buffer; // realloc successful
void *newbuf = p_realloc(ptr, size); // try realloc first
if (newbuf) return newbuf; // realloc successful
p_free(ptr); // free old buffer if realloc failed
return p_malloc(size); // fallback to malloc
}
#endif
#endif
// allocation function for buffers like pixel-buffers and segment data
// optimises the use of memory types to balance speed and heap availability, always favours DRAM if possible
// if multiple conflicting types are defined, the lowest bits of "type" take priority (see fcn_declare.h for types)
void *allocate_buffer(size_t size, uint32_t type) {
void *buffer = nullptr;
#ifdef CONFIG_IDF_TARGET_ESP32
// only classic ESP32 has "32bit accessible only" aka IRAM type. Using it frees up normal DRAM for other purposes
// this memory region is used for IRAM_ATTR functions, whatever is left is unused and can be used for pixel buffers
// prefer this type over PSRAM as it is slightly faster, except for _pixels where it is on-par as PSRAM-caching does a good job for mostly sequential access
if (type & BFRALLOC_NOBYTEACCESS) {
// prefer 32bit region, then PSRAM, fallback to any heap. Note: if adding "INTERNAL"-flag this wont work
buffer = heap_caps_malloc_prefer(size, 3, MALLOC_CAP_32BIT, MALLOC_CAP_SPIRAM, MALLOC_CAP_8BIT);
buffer = validateFreeHeap(buffer);
void *p_calloc(size_t count, size_t size) {
int caps1 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
int caps2 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
if (psramSafe) {
if (heap_caps_get_free_size(caps2) > 3*MIN_HEAP_SIZE && size < 512) std::swap(caps1, caps2); // use DRAM for small alloactions & when heap is plenty
return heap_caps_calloc_prefer(count, size, 2, caps1, caps2); // otherwise prefer PSRAM if it exists
}
else
#endif
#if !defined(BOARD_HAS_PSRAM)
buffer = d_malloc(size);
#else
if (type & BFRALLOC_PREFER_DRAM) {
if (getContiguousFreeHeap() < 3*(MIN_HEAP_SIZE/2) + size && size > PSRAM_THRESHOLD)
buffer = p_malloc(size); // prefer PSRAM for large allocations & when DRAM is low
else
buffer = d_malloc(size); // allocate in DRAM if enough free heap is available, PSRAM as fallback
}
else if (type & BFRALLOC_ENFORCE_DRAM)
buffer = heap_caps_malloc(size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); // use DRAM only, otherwise return nullptr
else if (type & BFRALLOC_PREFER_PSRAM) {
// if DRAM is plenty, prefer it over PSRAM for speed, reserve enough DRAM for segment data: if MAX_SEGMENT_DATA is exceeded, always uses PSRAM
if (getContiguousFreeHeap() > 4*MIN_HEAP_SIZE + size + ((uint32_t)(MAX_SEGMENT_DATA - Segment::getUsedSegmentData())))
buffer = d_malloc(size);
else
buffer = p_malloc(size); // prefer PSRAM
}
else if (type & BFRALLOC_ENFORCE_PSRAM)
buffer = heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); // use PSRAM only, otherwise return nullptr
buffer = validateFreeHeap(buffer);
#endif
if (buffer && (type & BFRALLOC_CLEAR))
memset(buffer, 0, size); // clear allocated buffer
/*
#if !defined(ESP8266) && defined(WLED_DEBUG)
if (buffer) {
DEBUG_PRINTF_P(PSTR("*Buffer allocated: size:%d, address:%p"), size, (uintptr_t)buffer);
if ((uintptr_t)buffer > SOC_DRAM_LOW && (uintptr_t)buffer < SOC_DRAM_HIGH)
DEBUG_PRINTLN(F(" in DRAM"));
#ifndef CONFIG_IDF_TARGET_ESP32C3
else if ((uintptr_t)buffer > SOC_EXTRAM_DATA_LOW && (uintptr_t)buffer < SOC_EXTRAM_DATA_HIGH)
DEBUG_PRINTLN(F(" in PSRAM"));
#endif
#ifdef CONFIG_IDF_TARGET_ESP32
else if ((uintptr_t)buffer > SOC_IRAM_LOW && (uintptr_t)buffer < SOC_IRAM_HIGH)
DEBUG_PRINTLN(F(" in IRAM")); // only used on ESP32 (MALLOC_CAP_32BIT)
#else
else if ((uintptr_t)buffer > SOC_RTC_DRAM_LOW && (uintptr_t)buffer < SOC_RTC_DRAM_HIGH)
DEBUG_PRINTLN(F(" in RTCRAM")); // not available on ESP32
#endif
else
DEBUG_PRINTLN(F(" in ???")); // unknown (check soc.h for other memory regions)
} else
DEBUG_PRINTF_P(PSTR("Buffer allocation failed: size:%d\n"), size);
#endif
*/
return buffer;
return heap_caps_calloc(count, size, caps2);
}
void *d_malloc(size_t size) {
int caps1 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
int caps2 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
if (psramSafe) {
if (heap_caps_get_largest_free_block(caps1) < 3*MIN_HEAP_SIZE && size > MIN_HEAP_SIZE) std::swap(caps1, caps2); // prefer PSRAM for large alloactions & when DRAM is low
return heap_caps_malloc_prefer(size, 2, caps1, caps2); // otherwise prefer DRAM
}
return heap_caps_malloc(size, caps1);
}
void *d_realloc(void *ptr, size_t size) {
int caps1 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
int caps2 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
if (psramSafe) {
if (heap_caps_get_largest_free_block(caps1) < 3*MIN_HEAP_SIZE && size > MIN_HEAP_SIZE) std::swap(caps1, caps2); // prefer PSRAM for large alloactions & when DRAM is low
return heap_caps_realloc_prefer(ptr, size, 2, caps1, caps2); // otherwise prefer DRAM
}
return heap_caps_realloc(ptr, size, caps1);
}
// realloc with malloc fallback, original buffer is freed if realloc fails but not copied!
void *d_realloc_malloc(void *ptr, size_t size) {
void *newbuf = d_realloc(ptr, size); // try realloc first
if (newbuf) return newbuf; // realloc successful
d_free(ptr); // free old buffer if realloc failed
return d_malloc(size); // fallback to malloc
}
void *d_calloc(size_t count, size_t size) {
int caps1 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
int caps2 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
if (psramSafe) {
if (size > MIN_HEAP_SIZE) std::swap(caps1, caps2); // prefer PSRAM for large alloactions
return heap_caps_calloc_prefer(count, size, 2, caps1, caps2); // otherwise prefer DRAM
}
return heap_caps_calloc(count, size, caps1);
}
#else // ESP8266 & ESP32-C3
// realloc with malloc fallback, original buffer is freed if realloc fails but not copied!
void *realloc_malloc(void *ptr, size_t size) {
void *newbuf = realloc(ptr, size); // try realloc first
if (newbuf) return newbuf; // realloc successful
free(ptr); // free old buffer if realloc failed
return malloc(size); // fallback to malloc
}
#endif
// bootloop detection and handling
// checks if the ESP reboots multiple times due to a crash or watchdog timeout
// if a bootloop is detected: restore settings from backup, then reset settings, then switch boot image (and repeat)
@@ -896,8 +802,7 @@ static bool detectBootLoop() {
bl_crashcounter++;
if (bl_crashcounter >= BOOTLOOP_THRESHOLD) {
DEBUG_PRINTLN(F("!BOOTLOOP DETECTED!"));
bl_crashcounter = 0;
if(bl_actiontracker > BOOTLOOP_ACTION_DUMP) bl_actiontracker = BOOTLOOP_ACTION_RESTORE; // reset action tracker if out of bounds
bl_crashcounter = 0;
result = true;
}
} else {

View File

@@ -171,7 +171,7 @@ void WLED::loop()
// reconnect WiFi to clear stale allocations if heap gets too low
if (millis() - heapTime > 15000) {
uint32_t heap = getFreeHeapSize();
uint32_t heap = ESP.getFreeHeap();
if (heap < MIN_HEAP_SIZE && lastHeap < MIN_HEAP_SIZE) {
DEBUG_PRINTF_P(PSTR("Heap too low! %u\n"), heap);
forceReconnect = true;
@@ -241,37 +241,13 @@ void WLED::loop()
DEBUG_PRINTLN(F("---DEBUG INFO---"));
DEBUG_PRINTF_P(PSTR("Runtime: %lu\n"), millis());
DEBUG_PRINTF_P(PSTR("Unix time: %u,%03u\n"), toki.getTime().sec, toki.getTime().ms);
DEBUG_PRINTF_P(PSTR("Free heap: %u\n"), ESP.getFreeHeap());
#if defined(ARDUINO_ARCH_ESP32)
DEBUG_PRINTLN(F("=== Memory Info ==="));
// Internal DRAM (standard 8-bit accessible heap)
size_t dram_free = heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
size_t dram_largest = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
DEBUG_PRINTF_P(PSTR("DRAM 8-bit: Free: %7u bytes | Largest block: %7u bytes\n"), dram_free, dram_largest);
#ifdef BOARD_HAS_PSRAM
size_t psram_free = heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
size_t psram_largest = heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM);
DEBUG_PRINTF_P(PSTR("PSRAM: Free: %7u bytes | Largest block: %6u bytes\n"), psram_free, psram_largest);
#endif
#if defined(CONFIG_IDF_TARGET_ESP32)
// 32-bit DRAM (not byte accessible, only available on ESP32)
size_t dram32_free = heap_caps_get_free_size(MALLOC_CAP_32BIT | MALLOC_CAP_INTERNAL) - dram_free; // returns all 32bit DRAM, subtract 8bit DRAM
//size_t dram32_largest = heap_caps_get_largest_free_block(MALLOC_CAP_32BIT | MALLOC_CAP_INTERNAL); // returns largest DRAM block -> not useful
DEBUG_PRINTF_P(PSTR("DRAM 32-bit: Free: %7u bytes | Largest block: N/A\n"), dram32_free);
#else
// Fast RTC Memory (not available on ESP32)
size_t rtcram_free = heap_caps_get_free_size(MALLOC_CAP_RTCRAM);
size_t rtcram_largest = heap_caps_get_largest_free_block(MALLOC_CAP_RTCRAM);
DEBUG_PRINTF_P(PSTR("RTC RAM: Free: %7u bytes | Largest block: %7u bytes\n"), rtcram_free, rtcram_largest);
#endif
if (psramFound()) {
DEBUG_PRINTF_P(PSTR("PSRAM: %dkB/%dkB\n"), ESP.getFreePsram()/1024, ESP.getPsramSize()/1024);
#ifndef BOARD_HAS_PSRAM
DEBUG_PRINTLN(F("BOARD_HAS_PSRAM not defined, not using PSRAM."));
#endif
if (!psramSafe) DEBUG_PRINTLN(F("Not using PSRAM."));
}
DEBUG_PRINTF_P(PSTR("TX power: %d/%d\n"), WiFi.getTxPower(), txPower);
#else // ESP8266
DEBUG_PRINTF_P(PSTR("Free heap/contiguous: %u/%u\n"), getFreeHeapSize(), getContiguousFreeHeap());
#endif
DEBUG_PRINTF_P(PSTR("Wifi state: %d\n"), WiFi.status());
#ifndef WLED_DISABLE_ESPNOW
@@ -391,16 +367,20 @@ void WLED::setup()
DEBUG_PRINTF_P(PSTR("esp8266 @ %u MHz.\nCore: %s\n"), ESP.getCpuFreqMHz(), ESP.getCoreVersion());
DEBUG_PRINTF_P(PSTR("FLASH: %u MB\n"), (ESP.getFlashChipSize()/1024)/1024);
#endif
DEBUG_PRINTF_P(PSTR("heap %u\n"), getFreeHeapSize());
#if defined(BOARD_HAS_PSRAM)
// if JSON buffer allocation fails requestJsonBufferLock() will always return false preventing crashes
pDoc = new PSRAMDynamicJsonDocument(2 * JSON_BUFFER_SIZE);
DEBUG_PRINTF_P(PSTR("JSON buffer size: %ubytes\n"), (2 * JSON_BUFFER_SIZE));
DEBUG_PRINTF_P(PSTR("PSRAM: %dkB/%dkB\n"), ESP.getFreePsram()/1024, ESP.getPsramSize()/1024);
#endif
DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap());
#if defined(ARDUINO_ARCH_ESP32)
// BOARD_HAS_PSRAM also means that a compiler flag "-mfix-esp32-psram-cache-issue" was used and so PSRAM is safe to use on rev.1 ESP32
#if !defined(BOARD_HAS_PSRAM) && !(defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3))
if (psramFound() && ESP.getChipRevision() < 3) psramSafe = false;
if (!psramSafe) DEBUG_PRINTLN(F("Not using PSRAM."));
#endif
pDoc = new PSRAMDynamicJsonDocument((psramSafe && psramFound() ? 2 : 1)*JSON_BUFFER_SIZE);
DEBUG_PRINTF_P(PSTR("JSON buffer allocated: %u\n"), (psramSafe && psramFound() ? 2 : 1)*JSON_BUFFER_SIZE);
// if the above fails requestJsonBufferLock() will always return false preventing crashes
if (psramFound()) {
DEBUG_PRINTF_P(PSTR("PSRAM: %dkB/%dkB\n"), ESP.getFreePsram()/1024, ESP.getPsramSize()/1024);
}
DEBUG_PRINTF_P(PSTR("TX power: %d/%d\n"), WiFi.getTxPower(), txPower);
#endif
@@ -415,7 +395,7 @@ void WLED::setup()
PinManager::allocatePin(2, true, PinOwner::DMX);
#endif
DEBUG_PRINTF_P(PSTR("heap %u\n"), getFreeHeapSize());
DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap());
bool fsinit = false;
DEBUGFS_PRINTLN(F("Mount FS"));
@@ -453,7 +433,7 @@ void WLED::setup()
}
DEBUG_PRINTLN(F("Reading config"));
bool needsCfgSave = deserializeConfigFromFS();
DEBUG_PRINTF_P(PSTR("heap %u\n"), getFreeHeapSize());
DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap());
#if defined(STATUSLED) && STATUSLED>=0
if (!PinManager::isPinAllocated(STATUSLED)) {
@@ -465,16 +445,16 @@ void WLED::setup()
DEBUG_PRINTLN(F("Initializing strip"));
beginStrip();
DEBUG_PRINTF_P(PSTR("heap %u\n"), getFreeHeapSize());
DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap());
DEBUG_PRINTLN(F("Usermods setup"));
userSetup();
UsermodManager::setup();
DEBUG_PRINTF_P(PSTR("heap %u\n"), getFreeHeapSize());
DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap());
if (needsCfgSave) serializeConfigToFS(); // usermods required new parameters; need to wait for strip to be initialised #4752
if (strcmp(multiWiFi[0].clientSSID, DEFAULT_CLIENT_SSID) == 0 && !configBackupExists())
if (strcmp(multiWiFi[0].clientSSID, DEFAULT_CLIENT_SSID) == 0)
showWelcomePage = true;
WiFi.persistent(false);
WiFi.onEvent(WiFiEvent);
@@ -535,13 +515,13 @@ void WLED::setup()
// HTTP server page init
DEBUG_PRINTLN(F("initServer"));
initServer();
DEBUG_PRINTF_P(PSTR("heap %u\n"), getFreeHeapSize());
DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap());
#ifndef WLED_DISABLE_INFRARED
// init IR
DEBUG_PRINTLN(F("initIR"));
initIR();
DEBUG_PRINTF_P(PSTR("heap %u\n"), getFreeHeapSize());
DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap());
#endif
// Seed FastLED random functions with an esp random value, which already works properly at this point.

View File

@@ -7,7 +7,7 @@
*/
// version code in format yymmddb (b = daily build)
#define VERSION 2506160
#define VERSION 2412040
//uncomment this if you have a "my_config.h" file you'd like to use
//#define WLED_USE_MY_CONFIG
@@ -167,13 +167,16 @@
// The following is a construct to enable code to compile without it.
// There is a code that will still not use PSRAM though:
// AsyncJsonResponse is a derived class that implements DynamicJsonDocument (AsyncJson-v6.h)
#if defined(BOARD_HAS_PSRAM)
#if defined(ARDUINO_ARCH_ESP32)
extern bool psramSafe;
struct PSRAM_Allocator {
void* allocate(size_t size) {
return ps_malloc(size); // use PSRAM
if (psramSafe && psramFound()) return ps_malloc(size); // use PSRAM if it exists
else return malloc(size); // fallback
}
void* reallocate(void* ptr, size_t new_size) {
return ps_realloc(ptr, new_size); // use PSRAM
if (psramSafe && psramFound()) return ps_realloc(ptr, new_size); // use PSRAM if it exists
else return realloc(ptr, new_size); // fallback
}
void deallocate(void* pointer) {
free(pointer);
@@ -276,14 +279,10 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument<PSRAM_Allocator>;
#ifndef WLED_RELEASE_NAME
#define WLED_RELEASE_NAME "Custom"
#endif
#ifndef WLED_REPO
#define WLED_REPO "unknown"
#endif
// Global Variable definitions
WLED_GLOBAL char versionString[] _INIT(TOSTRING(WLED_VERSION));
WLED_GLOBAL char releaseString[] _INIT(WLED_RELEASE_NAME); // must include the quotes when defining, e.g -D WLED_RELEASE_NAME=\"ESP32_MULTI_USREMODS\"
WLED_GLOBAL char repoString[] _INIT(WLED_REPO);
#define WLED_CODENAME "Niji"
// AP and OTA default passwords (for maximum security change them!)
@@ -895,6 +894,8 @@ WLED_GLOBAL byte optionType;
WLED_GLOBAL bool configNeedsWrite _INIT(false); // flag to initiate saving of config
WLED_GLOBAL bool doReboot _INIT(false); // flag to initiate reboot from async handlers
WLED_GLOBAL bool psramSafe _INIT(true); // is it safe to use PSRAM (on ESP32 rev.1; compiler fix used "-mfix-esp32-psram-cache-issue")
// status led
#if defined(STATUSLED)
WLED_GLOBAL unsigned long ledStatusLastMillis _INIT(0);
@@ -968,11 +969,8 @@ WLED_GLOBAL int8_t spi_sclk _INIT(SPISCLKPIN);
// global ArduinoJson buffer
#if defined(ARDUINO_ARCH_ESP32)
WLED_GLOBAL SemaphoreHandle_t jsonBufferLockMutex _INIT(xSemaphoreCreateRecursiveMutex());
#endif
#ifdef BOARD_HAS_PSRAM
// if board has PSRAM, use it for JSON document (allocated in setup())
WLED_GLOBAL JsonDocument *pDoc _INIT(nullptr);
WLED_GLOBAL SemaphoreHandle_t jsonBufferLockMutex _INIT(xSemaphoreCreateRecursiveMutex());
#else
WLED_GLOBAL StaticJsonDocument<JSON_BUFFER_SIZE> gDoc;
WLED_GLOBAL JsonDocument *pDoc _INIT(&gDoc);

View File

@@ -96,7 +96,7 @@ void loadSettingsFromEEPROM()
if (apHide > 1) apHide = 1;
uint16_t length = EEPROM.read(229) + ((EEPROM.read(398) << 8) & 0xFF00); //was ledCount
if (length > MAX_LEDS || length == 0) length = 30;
uint8_t pins[OUTPUT_MAX_PINS] = {2, 255, 255, 255, 255};
uint8_t pins[5] = {2, 255, 255, 255, 255};
uint8_t colorOrder = COL_ORDER_GRB;
if (lastEEPROMversion > 9) colorOrder = EEPROM.read(383);
if (colorOrder > COL_ORDER_GBR) colorOrder = COL_ORDER_GRB;

View File

@@ -349,13 +349,8 @@ void initServer()
if (verboseResponse) {
if (!isConfig) {
lastInterfaceUpdate = millis(); // prevent WS update until cooldown
interfaceUpdateCallMode = CALL_MODE_WS_SEND; // override call mode & schedule WS update
#ifndef WLED_DISABLE_MQTT
// publish state to MQTT as requested in wled#4643 even if only WS response selected
publishMqtt();
#endif
serveJson(request);
return; //if JSON contains "v"
interfaceUpdateCallMode = CALL_MODE_WS_SEND; // schedule WS update
serveJson(request); return; //if JSON contains "v"
} else {
configNeedsWrite = true; //Save new settings to FS
}
@@ -373,7 +368,7 @@ void initServer()
});
server.on(F("/freeheap"), HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, FPSTR(CONTENT_TYPE_PLAIN), (String)getFreeHeapSize());
request->send(200, FPSTR(CONTENT_TYPE_PLAIN), (String)ESP.getFreeHeap());
});
#ifdef WLED_ENABLE_USERMOD_PAGE
@@ -475,31 +470,29 @@ void initServer()
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
if (captivePortal(request)) return;
if (!showWelcomePage || request->hasArg(F("sliders"))) {
handleStaticContent(request, F("/index.htm"), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_index, PAGE_index_length);
handleStaticContent(request, F("/index.htm"), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_index, PAGE_index_L);
} else {
serveSettings(request);
}
});
#ifndef WLED_DISABLE_2D
#ifdef WLED_ENABLE_PIXART
#ifdef WLED_ENABLE_PIXART
static const char _pixart_htm[] PROGMEM = "/pixart.htm";
server.on(_pixart_htm, HTTP_GET, [](AsyncWebServerRequest *request) {
handleStaticContent(request, FPSTR(_pixart_htm), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_pixart, PAGE_pixart_length);
handleStaticContent(request, FPSTR(_pixart_htm), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_pixart, PAGE_pixart_L);
});
#endif
#endif
#ifndef WLED_DISABLE_PXMAGIC
#ifndef WLED_DISABLE_PXMAGIC
static const char _pxmagic_htm[] PROGMEM = "/pxmagic.htm";
server.on(_pxmagic_htm, HTTP_GET, [](AsyncWebServerRequest *request) {
handleStaticContent(request, FPSTR(_pxmagic_htm), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_pxmagic, PAGE_pxmagic_length);
handleStaticContent(request, FPSTR(_pxmagic_htm), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_pxmagic, PAGE_pxmagic_L);
});
#endif
#endif
static const char _cpal_htm[] PROGMEM = "/cpal.htm";
server.on(_cpal_htm, HTTP_GET, [](AsyncWebServerRequest *request) {
handleStaticContent(request, FPSTR(_cpal_htm), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_cpal, PAGE_cpal_length);
handleStaticContent(request, FPSTR(_cpal_htm), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_cpal, PAGE_cpal_L);
});
#ifdef WLED_ENABLE_WEBSOCKETS

View File

@@ -5,12 +5,6 @@
*/
#ifdef WLED_ENABLE_WEBSOCKETS
// define some constants for binary protocols, dont use defines but C++ style constexpr
constexpr uint8_t BINARY_PROTOCOL_GENERIC = 0xFF; // generic / auto detect NOT IMPLEMENTED
constexpr uint8_t BINARY_PROTOCOL_E131 = P_E131; // = 0, untested!
constexpr uint8_t BINARY_PROTOCOL_ARTNET = P_ARTNET; // = 1, untested!
constexpr uint8_t BINARY_PROTOCOL_DDP = P_DDP; // = 2
uint16_t wsLiveClientId = 0;
unsigned long wsLastLiveTime = 0;
//uint8_t* wsFrameBuffer = nullptr;
@@ -31,7 +25,7 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
// data packet
AwsFrameInfo * info = (AwsFrameInfo*)arg;
if(info->final && info->index == 0 && info->len == len){
// the whole message is in a single frame and we got all of its data (max. 1428 bytes / ESP8266: 528 bytes)
// the whole message is in a single frame and we got all of its data (max. 1450 bytes)
if(info->opcode == WS_TEXT)
{
if (len > 0 && len < 10 && data[0] == 'p') {
@@ -65,10 +59,6 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
if (!interfaceUpdateCallMode) { // individual client response only needed if no WS broadcast soon
if (verboseResponse) {
#ifndef WLED_DISABLE_MQTT
// publish state to MQTT as requested in wled#4643 even if only WS response selected
publishMqtt();
#endif
sendDataWs(client);
} else {
// we have to send something back otherwise WS connection closes
@@ -77,29 +67,8 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
// force broadcast in 500ms after updating client
//lastInterfaceUpdate = millis() - (INTERFACE_UPDATE_COOLDOWN -500); // ESP8266 does not like this
}
}else if (info->opcode == WS_BINARY) {
// first byte determines protocol. Note: since e131_packet_t is "packed", the compiler handles alignment issues
//DEBUG_PRINTF_P(PSTR("WS binary message: len %u, byte0: %u\n"), len, data[0]);
int offset = 1; // offset to skip protocol byte
switch (data[0]) {
case BINARY_PROTOCOL_E131:
handleE131Packet((e131_packet_t*)&data[offset], client->remoteIP(), P_E131);
break;
case BINARY_PROTOCOL_ARTNET:
handleE131Packet((e131_packet_t*)&data[offset], client->remoteIP(), P_ARTNET);
break;
case BINARY_PROTOCOL_DDP:
if (len < 10 + offset) return; // DDP header is 10 bytes (+1 protocol byte)
size_t ddpDataLen = (data[8+offset] << 8) | data[9+offset]; // data length in bytes from DDP header
uint8_t flags = data[0+offset];
if ((flags & DDP_TIMECODE_FLAG) ) ddpDataLen += 4; // timecode flag adds 4 bytes to data length
if (len < (10 + offset + ddpDataLen)) return; // not enough data, prevent out of bounds read
// could be a valid DDP packet, forward to handler
handleE131Packet((e131_packet_t*)&data[offset], client->remoteIP(), P_DDP);
}
}
} else {
DEBUG_PRINTF_P(PSTR("WS multipart message: final %u index %u len %u total %u\n"), info->final, info->index, len, (uint32_t)info->len);
//message is comprised of multiple frames or the frame is split into multiple packets
//if(info->index == 0){
//if (!wsFrameBuffer && len < 4096) wsFrameBuffer = new uint8_t[4096];
@@ -155,8 +124,8 @@ void sendDataWs(AsyncWebSocketClient * client)
DEBUG_PRINTF_P(PSTR("JSON buffer size: %u for WS request (%u).\n"), pDoc->memoryUsage(), len);
// the following may no longer be necessary as heap management has been fixed by @willmmiles in AWS
size_t heap1 = getFreeHeapSize();
DEBUG_PRINTF_P(PSTR("heap %u\n"), getFreeHeapSize());
size_t heap1 = ESP.getFreeHeap();
DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap());
#ifdef ESP8266
if (len>heap1) {
DEBUG_PRINTLN(F("Out of memory (WS)!"));
@@ -165,8 +134,8 @@ void sendDataWs(AsyncWebSocketClient * client)
#endif
AsyncWebSocketBuffer buffer(len);
#ifdef ESP8266
size_t heap2 = getFreeHeapSize();
DEBUG_PRINTF_P(PSTR("heap %u\n"), getFreeHeapSize());
size_t heap2 = ESP.getFreeHeap();
DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap());
#else
size_t heap2 = 0; // ESP32 variants do not have the same issue and will work without checking heap allocation
#endif

View File

@@ -331,11 +331,11 @@ void getSettingsJS(byte subPage, Print& settingsScript)
char ma[4] = "MA"; ma[2] = offset+s; ma[3] = 0; //max per-port PSU current
char hs[4] = "HS"; hs[2] = offset+s; hs[3] = 0; //hostname (for network types, custom text for others)
settingsScript.print(F("addLEDs(1);"));
uint8_t pins[OUTPUT_MAX_PINS];
uint8_t pins[5];
int nPins = bus->getPins(pins);
for (int i = 0; i < nPins; i++) {
lp[1] = '0'+i;
if (PinManager::isPinOk(pins[i]) || bus->isVirtual() || Bus::isHub75(bus->getType())) printSetFormValue(settingsScript,lp,pins[i]);
if (PinManager::isPinOk(pins[i]) || bus->isVirtual()) printSetFormValue(settingsScript,lp,pins[i]);
}
printSetFormValue(settingsScript,lc,bus->getLength());
printSetFormValue(settingsScript,lt,bus->getType());
@@ -457,8 +457,8 @@ void getSettingsJS(byte subPage, Print& settingsScript)
printSetFormCheckbox(settingsScript,PSTR("EM"),e131Multicast);
printSetFormValue(settingsScript,PSTR("EU"),e131Universe);
#ifdef WLED_ENABLE_DMX
settingsScript.print(SET_F("hideNoDMX();")); // hide "not compiled in" message
#endif
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