mirror of
https://github.com/wled/WLED.git
synced 2025-11-02 07:38:51 +00:00
Compare commits
1 Commits
main
...
remove-v1-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a21869362 |
3
.github/FUNDING.yml
vendored
3
.github/FUNDING.yml
vendored
@@ -1,3 +1,2 @@
|
||||
github: [Aircoookie,blazoncek,DedeHai,lost-hope,willmmiles]
|
||||
github: [Aircoookie,blazoncek]
|
||||
custom: ['https://paypal.me/Aircoookie','https://paypal.me/blazoncek']
|
||||
thanks_dev: u/gh/netmindz
|
||||
|
||||
172
.github/copilot-instructions.md
vendored
172
.github/copilot-instructions.md
vendored
@@ -1,172 +0,0 @@
|
||||
# WLED - ESP32/ESP8266 LED Controller Firmware
|
||||
|
||||
WLED is a fast and feature-rich implementation of an ESP32 and ESP8266 webserver to control NeoPixel (WS2812B, WS2811, SK6812) LEDs and SPI-based chipsets. The project consists of C++ firmware for microcontrollers and a modern web interface.
|
||||
|
||||
Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.
|
||||
|
||||
## Working Effectively
|
||||
|
||||
### Initial Setup
|
||||
- Install Node.js 20+ (specified in `.nvmrc`): Check your version with `node --version`
|
||||
- Install dependencies: `npm ci` (takes ~5 seconds)
|
||||
- Install PlatformIO for hardware builds: `pip install -r requirements.txt` (takes ~60 seconds)
|
||||
|
||||
### Build and Test Workflow
|
||||
- **ALWAYS build web UI first**: `npm run build` -- takes 3 seconds. NEVER CANCEL.
|
||||
- **Run tests**: `npm test` -- takes 40 seconds. NEVER CANCEL. Set timeout to 2+ minutes.
|
||||
- **Development mode**: `npm run dev` -- monitors file changes and auto-rebuilds web UI
|
||||
- **Hardware firmware build**: `pio run -e [environment]` -- takes 15+ minutes. NEVER CANCEL. Set timeout to 30+ minutes.
|
||||
|
||||
### Build Process Details
|
||||
The build has two main phases:
|
||||
1. **Web UI Generation** (`npm run build`):
|
||||
- Processes files in `wled00/data/` (HTML, CSS, JS)
|
||||
- Minifies and compresses web content
|
||||
- Generates `wled00/html_*.h` files with embedded web content
|
||||
- **CRITICAL**: Must be done before any hardware build
|
||||
|
||||
2. **Hardware Compilation** (`pio run`):
|
||||
- Compiles C++ firmware for various ESP32/ESP8266 targets
|
||||
- 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
|
||||
- **ALWAYS validate web UI changes manually**:
|
||||
- Start local server: `cd wled00/data && python3 -m http.server 8080`
|
||||
- Open `http://localhost:8080/index.htm` in browser
|
||||
- Test basic functionality: color picker, effects, settings pages
|
||||
- **Check for JavaScript errors** in browser console
|
||||
|
||||
### Code Validation
|
||||
- **No automated linting configured** - follow existing code style in files you edit
|
||||
- **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:
|
||||
- **Load main interface**: Verify index.htm loads without errors
|
||||
- **Navigation**: Test switching between main page and settings pages
|
||||
- **Color controls**: Verify color picker and brightness controls work
|
||||
- **Effects**: Test effect selection and parameter changes
|
||||
- **Settings**: Test form submission and validation
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Repository Structure
|
||||
```
|
||||
wled00/ # Main firmware source (C++)
|
||||
├── data/ # Web interface files
|
||||
│ ├── index.htm # Main UI
|
||||
│ ├── settings*.htm # Settings pages
|
||||
│ └── *.js/*.css # Frontend resources
|
||||
├── *.cpp/*.h # Firmware source files
|
||||
└── html_*.h # Generated embedded web files (DO NOT EDIT)
|
||||
tools/ # Build tools (Node.js)
|
||||
├── cdata.js # Web UI build script
|
||||
└── cdata-test.js # Test suite
|
||||
platformio.ini # Hardware build configuration
|
||||
package.json # Node.js dependencies and scripts
|
||||
.github/workflows/ # CI/CD pipelines
|
||||
```
|
||||
|
||||
### Key Files and Their Purpose
|
||||
- `wled00/data/index.htm` - Main web interface
|
||||
- `wled00/data/settings*.htm` - Configuration pages
|
||||
- `tools/cdata.js` - Converts web files to C++ headers
|
||||
- `wled00/wled.h` - Main firmware configuration
|
||||
- `platformio.ini` - Hardware build targets and settings
|
||||
|
||||
### Development Workflow
|
||||
1. **For web UI changes**:
|
||||
- Edit files in `wled00/data/`
|
||||
- Run `npm run build` to regenerate headers
|
||||
- Test with local HTTP server
|
||||
- Run `npm test` to validate build system
|
||||
|
||||
2. **For firmware changes**:
|
||||
- Edit files in `wled00/` (but NOT `html_*.h` files)
|
||||
- Ensure web UI is built first (`npm run build`)
|
||||
- Build firmware: `pio run -e [target]`
|
||||
- Flash to device: `pio run -e [target] --target upload`
|
||||
|
||||
3. **For both web and firmware**:
|
||||
- Always build web UI first
|
||||
- Test web interface manually
|
||||
- Build and test firmware if making firmware changes
|
||||
|
||||
## 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.**
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
- **Build fails with missing html_*.h**: Run `npm run build` first
|
||||
- **Web UI looks broken**: Check browser console for JavaScript errors
|
||||
- **PlatformIO network errors**: Try again, downloads can be flaky
|
||||
- **Node.js version issues**: Ensure Node.js 20+ is installed (check `.nvmrc`)
|
||||
|
||||
### When Things Go Wrong
|
||||
- **Clear generated files**: `rm -f wled00/html_*.h` then rebuild
|
||||
- **Force web UI rebuild**: `npm run build -- --force` or `npm run build -- -f`
|
||||
- **Clean PlatformIO cache**: `pio run --target clean`
|
||||
- **Reinstall dependencies**: `rm -rf node_modules && npm install`
|
||||
|
||||
## Important Notes
|
||||
|
||||
- **DO NOT edit `wled00/html_*.h` files** - they are auto-generated
|
||||
- **Always commit both source files AND generated html_*.h files**
|
||||
- **Web UI must be built before firmware compilation**
|
||||
- **Test web interface manually after any web UI changes**
|
||||
- **Use VS Code with PlatformIO extension for best development experience**
|
||||
- **Hardware builds require appropriate ESP32/ESP8266 development board**
|
||||
|
||||
## CI/CD Pipeline
|
||||
|
||||
**The GitHub Actions CI workflow will:**
|
||||
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)
|
||||
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.**
|
||||
5
.github/workflows/build.yml
vendored
5
.github/workflows/build.yml
vendored
@@ -40,10 +40,7 @@ jobs:
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
- run: |
|
||||
npm ci
|
||||
VERSION=`date +%y%m%d0`
|
||||
sed -i -r -e "s/define VERSION .+/define VERSION $VERSION/" wled00/wled.h
|
||||
- run: npm ci
|
||||
- name: Cache PlatformIO
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
|
||||
2
.github/workflows/nightly.yml
vendored
2
.github/workflows/nightly.yml
vendored
@@ -27,8 +27,6 @@ jobs:
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
sinceTag: v0.15.0
|
||||
# Exclude issues that were closed without resolution from changelog
|
||||
exclude-labels: 'stale,wontfix,duplicate,invalid'
|
||||
- name: Update Nightly Release
|
||||
uses: andelf/nightly-release@main
|
||||
env:
|
||||
|
||||
19
.github/workflows/pr-merge.yaml
vendored
19
.github/workflows/pr-merge.yaml
vendored
@@ -1,13 +1,12 @@
|
||||
name: Notify Discord on PR Merge
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request_target:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
|
||||
jobs:
|
||||
notify:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.merged == true
|
||||
steps:
|
||||
- name: Get User Permission
|
||||
id: checkAccess
|
||||
@@ -24,15 +23,11 @@
|
||||
echo "Current permission level is ${{ steps.checkAccess.outputs.user-permission }}"
|
||||
echo "Job originally triggered by ${{ github.actor }}"
|
||||
exit 1
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }} # This is dangerous without the first access check
|
||||
- name: Send Discord notification
|
||||
env:
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
ACTOR: ${{ github.actor }}
|
||||
# if: github.event.pull_request.merged == true
|
||||
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" \
|
||||
'{content: $content}' \
|
||||
| curl -H "Content-Type: application/json" -d @- ${{ secrets.DISCORD_WEBHOOK_BETA_TESTERS }}
|
||||
curl -H "Content-Type: application/json" -d '{"content": "Pull Request ${{ github.event.pull_request.number }} merged by ${{ github.actor }}"}' ${{ secrets.DISCORD_WEBHOOK_BETA_TESTERS }}
|
||||
|
||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -24,9 +24,7 @@ jobs:
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
sinceTag: v0.15.0
|
||||
maxIssues: 500
|
||||
# Exclude issues that were closed without resolution from changelog
|
||||
exclude-labels: 'stale,wontfix,duplicate,invalid'
|
||||
maxIssues: 500
|
||||
- name: Create draft release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
|
||||
3
.github/workflows/usermods.yml
vendored
3
.github/workflows/usermods.yml
vendored
@@ -5,9 +5,6 @@ on:
|
||||
paths:
|
||||
- usermods/**
|
||||
- .github/workflows/usermods.yml
|
||||
pull_request:
|
||||
paths:
|
||||
- usermods/**
|
||||
|
||||
jobs:
|
||||
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -7,8 +7,6 @@
|
||||
.pioenvs
|
||||
.piolibdeps
|
||||
.vscode
|
||||
compile_commands.json
|
||||
__pycache__/
|
||||
|
||||
esp01-update.sh
|
||||
platformio_override.ini
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"name": "NeoESP32RmtHI",
|
||||
"build": { "libArchive": false },
|
||||
"platforms": ["espressif32"],
|
||||
"dependencies": [
|
||||
{
|
||||
"owner": "makuna",
|
||||
"name": "NeoPixelBus",
|
||||
"version": "^2.8.3"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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}\\"'])
|
||||
@@ -10,7 +10,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# CI/release binaries
|
||||
default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover, usermods
|
||||
default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev, esp32dev_V4, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover, usermods
|
||||
|
||||
src_dir = ./wled00
|
||||
data_dir = ./wled00/data
|
||||
@@ -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,8 +141,8 @@ lib_deps =
|
||||
fastled/FastLED @ 3.6.0
|
||||
IRremoteESP8266 @ 2.8.2
|
||||
makuna/NeoPixelBus @ 2.8.3
|
||||
https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.4.2
|
||||
marvinroger/AsyncMqttClient @ 0.9.0
|
||||
#https://github.com/makuna/NeoPixelBus.git#CoreShaderBeta
|
||||
https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.4.0
|
||||
# for I2C interface
|
||||
;Wire
|
||||
# ESP-NOW library
|
||||
@@ -235,20 +234,25 @@ lib_deps_compat =
|
||||
|
||||
[esp32_all_variants]
|
||||
lib_deps =
|
||||
esp32async/AsyncTCP @ 3.4.7
|
||||
willmmiles/AsyncTCP @ 1.3.1
|
||||
bitbank2/AnimatedGIF@^1.4.7
|
||||
https://github.com/Aircoookie/GifDecoder#bc3af18
|
||||
build_flags =
|
||||
-D CONFIG_ASYNC_TCP_USE_WDT=0
|
||||
-D CONFIG_ASYNC_TCP_STACK_SIZE=8192
|
||||
-D WLED_ENABLE_GIF
|
||||
|
||||
[esp32]
|
||||
platform = ${esp32_idf_V4.platform}
|
||||
platform_packages =
|
||||
#platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.3/platform-espressif32-2.0.2.3.zip
|
||||
platform = espressif32@3.5.0
|
||||
platform_packages = framework-arduinoespressif32 @ https://github.com/Aircoookie/arduino-esp32.git#1.0.6.4
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${esp32_idf_V4.build_flags}
|
||||
lib_deps = ${esp32_idf_V4.lib_deps}
|
||||
build_flags = -g
|
||||
-DARDUINO_ARCH_ESP32
|
||||
#-DCONFIG_LITTLEFS_FOR_IDF_3_2
|
||||
#use LITTLEFS library by lorol in ESP32 core 1.x.x instead of built-in in 2.x.x
|
||||
-D LOROL_LITTLEFS
|
||||
; -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
|
||||
${esp32_all_variants.build_flags}
|
||||
|
||||
tiny_partitions = tools/WLED_ESP32_2MB_noOTA.csv
|
||||
default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
|
||||
@@ -256,7 +260,10 @@ extended_partitions = tools/WLED_ESP32_4MB_700k_FS.csv
|
||||
big_partitions = tools/WLED_ESP32_4MB_256KB_FS.csv ;; 1.8MB firmware, 256KB filesystem, coredump support
|
||||
large_partitions = tools/WLED_ESP32_8MB.csv
|
||||
extreme_partitions = tools/WLED_ESP32_16MB_9MB_FS.csv
|
||||
|
||||
lib_deps =
|
||||
https://github.com/lorol/LITTLEFS.git
|
||||
${esp32_all_variants.lib_deps}
|
||||
${env.lib_deps}
|
||||
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs
|
||||
# additional build flags for audioreactive - must be applied globally
|
||||
AR_build_flags = ;; -fsingle-precision-constant ;; forces ArduinoFFT to use float math (2x faster)
|
||||
@@ -264,7 +271,8 @@ AR_lib_deps = ;; for pre-usermod-library platformio_override compatibility
|
||||
|
||||
|
||||
[esp32_idf_V4]
|
||||
;; build environment for ESP32 using ESP-IDF 4.4.x / arduino-esp32 v2.0.5
|
||||
;; experimental build environment for ESP32 using ESP-IDF 4.4.x / arduino-esp32 v2.0.5
|
||||
;; very similar to the normal ESP32 flags, but omitting Lorol LittleFS, as littlefs is included in the new framework already.
|
||||
;;
|
||||
;; please note that you can NOT update existing ESP32 installs with a "V4" build. Also updating by OTA will not work properly.
|
||||
;; You need to completely erase your device (esptool erase_flash) first, then install the "V4" build from VSCode+platformio.
|
||||
@@ -275,12 +283,14 @@ build_unflags = ${common.build_unflags}
|
||||
build_flags = -g
|
||||
-Wshadow=compatible-local ;; emit warning in case a local variable "shadows" another local one
|
||||
-DARDUINO_ARCH_ESP32 -DESP32
|
||||
-DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
|
||||
${esp32_all_variants.build_flags}
|
||||
-D WLED_ENABLE_DMX_INPUT
|
||||
lib_deps =
|
||||
${esp32_all_variants.lib_deps}
|
||||
https://github.com/someweisguy/esp_dmx.git#47db25d
|
||||
${env.lib_deps}
|
||||
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs
|
||||
|
||||
[esp32s2]
|
||||
;; generic definitions for all ESP32-S2 boards
|
||||
@@ -295,9 +305,10 @@ build_flags = -g
|
||||
-DARDUINO_USB_MODE=0 ;; this flag is mandatory for ESP32-S2 !
|
||||
;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry:
|
||||
;; ARDUINO_USB_CDC_ON_BOOT
|
||||
${esp32_idf_V4.build_flags}
|
||||
${esp32_all_variants.build_flags}
|
||||
lib_deps =
|
||||
${esp32_idf_V4.lib_deps}
|
||||
${esp32_all_variants.lib_deps}
|
||||
${env.lib_deps}
|
||||
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs
|
||||
|
||||
[esp32c3]
|
||||
@@ -312,9 +323,10 @@ build_flags = -g
|
||||
-DARDUINO_USB_MODE=1 ;; this flag is mandatory for ESP32-C3
|
||||
;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry:
|
||||
;; ARDUINO_USB_CDC_ON_BOOT
|
||||
${esp32_idf_V4.build_flags}
|
||||
${esp32_all_variants.build_flags}
|
||||
lib_deps =
|
||||
${esp32_idf_V4.lib_deps}
|
||||
${esp32_all_variants.lib_deps}
|
||||
${env.lib_deps}
|
||||
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs
|
||||
board_build.flash_mode = qio
|
||||
|
||||
@@ -331,9 +343,10 @@ build_flags = -g
|
||||
-DCO
|
||||
;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry:
|
||||
;; ARDUINO_USB_MODE, ARDUINO_USB_CDC_ON_BOOT
|
||||
${esp32_idf_V4.build_flags}
|
||||
${esp32_all_variants.build_flags}
|
||||
lib_deps =
|
||||
${esp32_idf_V4.lib_deps}
|
||||
${esp32_all_variants.lib_deps}
|
||||
${env.lib_deps}
|
||||
board_build.partitions = ${esp32.large_partitions} ;; default partioning for 8MB flash - can be overridden in build envs
|
||||
|
||||
|
||||
@@ -428,11 +441,21 @@ custom_usermods = audioreactive
|
||||
|
||||
[env:esp32dev]
|
||||
board = esp32dev
|
||||
platform = ${esp32.platform}
|
||||
platform_packages = ${esp32.platform_packages}
|
||||
custom_usermods = audioreactive
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32\" #-D WLED_DISABLE_BROWNOUT_DET
|
||||
lib_deps = ${esp32.lib_deps}
|
||||
monitor_filters = esp32_exception_decoder
|
||||
board_build.partitions = ${esp32.default_partitions}
|
||||
|
||||
[env:esp32dev_V4]
|
||||
board = esp32dev
|
||||
platform = ${esp32_idf_V4.platform}
|
||||
build_unflags = ${common.build_unflags}
|
||||
custom_usermods = audioreactive
|
||||
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_V4\" #-D WLED_DISABLE_BROWNOUT_DET
|
||||
-DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
|
||||
lib_deps = ${esp32_idf_V4.lib_deps}
|
||||
monitor_filters = esp32_exception_decoder
|
||||
board_build.partitions = ${esp32.default_partitions}
|
||||
@@ -450,7 +473,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
|
||||
@@ -466,9 +489,23 @@ board_upload.maximum_size = 16777216
|
||||
board_build.f_flash = 80000000L
|
||||
board_build.flash_mode = dio
|
||||
|
||||
;[env:esp32dev_audioreactive]
|
||||
;board = esp32dev
|
||||
;platform = ${esp32.platform}
|
||||
;platform_packages = ${esp32.platform_packages}
|
||||
;custom_usermods = audioreactive
|
||||
;build_unflags = ${common.build_unflags}
|
||||
;build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32_audioreactive\" #-D WLED_DISABLE_BROWNOUT_DET
|
||||
;lib_deps = ${esp32.lib_deps}
|
||||
;monitor_filters = esp32_exception_decoder
|
||||
;board_build.partitions = ${esp32.default_partitions}
|
||||
;; board_build.f_flash = 80000000L
|
||||
;; board_build.flash_mode = dio
|
||||
|
||||
[env:esp32_eth]
|
||||
board = esp32-poe
|
||||
platform = ${esp32_idf_V4.platform}
|
||||
platform = ${esp32.platform}
|
||||
platform_packages = ${esp32.platform_packages}
|
||||
upload_speed = 921600
|
||||
custom_usermods = audioreactive
|
||||
build_unflags = ${common.build_unflags}
|
||||
@@ -476,10 +513,10 @@ build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"
|
||||
; -D WLED_DISABLE_ESPNOW ;; ESP-NOW requires wifi, may crash with ethernet only
|
||||
lib_deps = ${esp32.lib_deps}
|
||||
board_build.partitions = ${esp32.default_partitions}
|
||||
board_build.flash_mode = dio
|
||||
|
||||
[env:esp32_wrover]
|
||||
extends = esp32_idf_V4
|
||||
platform = ${esp32_idf_V4.platform}
|
||||
board = ttgo-t7-v14-mini32
|
||||
board_build.f_flash = 80000000L
|
||||
board_build.flash_mode = qio
|
||||
|
||||
@@ -28,6 +28,8 @@ 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
|
||||
; bitbank2/PNGdec@^1.0.1 ;; used for POV display uncomment following
|
||||
; ${esp32.AR_lib_deps} ;; needed for USERMOD_AUDIOREACTIVE
|
||||
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags} ${esp8266.build_flags}
|
||||
@@ -139,6 +141,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 +181,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 +242,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 +256,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 +376,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 +392,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 +526,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 +536,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
|
||||
|
||||
@@ -10,12 +10,10 @@
|
||||
|
||||
</p>
|
||||
|
||||
# Welcome to WLED! ✨
|
||||
# Welcome to my project WLED! ✨
|
||||
|
||||
A fast and feature-rich implementation of an ESP32 and ESP8266 webserver to control NeoPixel (WS2812B, WS2811, SK6812) LEDs or also SPI based chipsets like the WS2801 and APA102!
|
||||
|
||||
Originally created by [Aircoookie](https://github.com/Aircoookie)
|
||||
|
||||
## ⚙️ Features
|
||||
- WS2812FX library with more than 100 special effects
|
||||
- FastLED noise effects and 50 palettes
|
||||
@@ -34,7 +32,7 @@ Originally created by [Aircoookie](https://github.com/Aircoookie)
|
||||
- Filesystem-based config for easier backup of presets and settings
|
||||
|
||||
## 💡 Supported light control interfaces
|
||||
- WLED app for [Android](https://play.google.com/store/apps/details?id=ca.cgagnier.wlednativeandroid) and [iOS](https://apps.apple.com/gb/app/wled-native/id6446207239)
|
||||
- WLED app for [Android](https://play.google.com/store/apps/details?id=com.aircoookie.WLED) and [iOS](https://apps.apple.com/us/app/wled/id1475695033)
|
||||
- JSON and HTTP request APIs
|
||||
- MQTT
|
||||
- E1.31, Art-Net, DDP and TPM2.net
|
||||
@@ -65,7 +63,6 @@ See [here](https://kno.wled.ge/basics/compatible-hardware)!
|
||||
|
||||
Licensed under the EUPL v1.2 license
|
||||
Credits [here](https://kno.wled.ge/about/contributors/)!
|
||||
CORS proxy by [Corsfix](https://corsfix.com/)
|
||||
|
||||
Join the Discord server to discuss everything about WLED!
|
||||
|
||||
|
||||
@@ -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",
|
||||
[
|
||||
|
||||
113
tools/wled-tools
113
tools/wled-tools
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
default_envs = usermods_esp32, usermods_esp32c3, usermods_esp32s2, usermods_esp32s3
|
||||
|
||||
[env:usermods_esp32]
|
||||
extends = env:esp32dev
|
||||
extends = env:esp32dev_V4
|
||||
custom_usermods = ${usermods.custom_usermods}
|
||||
board_build.partitions = ${esp32.extreme_partitions} ; We're gonna need a bigger boat
|
||||
|
||||
@@ -28,4 +28,4 @@ board_build.partitions = ${esp32.extreme_partitions} ; We're gonna need a bigge
|
||||
|
||||
|
||||
[usermods]
|
||||
# Added in CI
|
||||
# Added in CI
|
||||
@@ -1,48 +0,0 @@
|
||||
## POV Display usermod
|
||||
|
||||
This usermod adds a new effect called “POV Image”.
|
||||
|
||||

|
||||
|
||||
###How does it work?
|
||||
With proper configuration (see below) the main segment will display a single row of pixels from an image stored on the ESP.
|
||||
It displays the image row by row at a high refresh rate.
|
||||
If you move the pixel segment at the right speed, you will see the full image floating in the air thanks to the persistence of vision.
|
||||
RGB LEDs only (no RGBW), with grouping set to 1 and spacing set to 0.
|
||||
Best results with high-density strips (e.g., 144 LEDs/m).
|
||||
|
||||
To get it working:
|
||||
- Resize your image. The height must match the number of LEDs in your strip/segment.
|
||||
- Rotate your image 90° clockwise (height becomes width).
|
||||
- Upload a BMP image (24-bit, uncompressed) to the ESP filesystem using the “/edit” URL.
|
||||
- Select the “POV Image” effect.
|
||||
- Set the segment name to the absolute filesystem path of the image (e.g., “/myimage.bmp”).
|
||||
- The path is case-sensitive and must start with “/”.
|
||||
- Rotate the pixel strip at approximately 20 RPM.
|
||||
- Tune as needed so that one full revolution maps to the image width (if the image appears stretched or compressed, adjust RPM slightly).
|
||||
- Enjoy the show!
|
||||
|
||||
Notes:
|
||||
- Only 24-bit uncompressed BMP files are supported.
|
||||
- The image must fit into ~64 KB of RAM (width × height × 3 bytes, plus row padding to a 4-byte boundary).
|
||||
- Examples (approximate, excluding row padding):
|
||||
- 128×128 (49,152 bytes) fits.
|
||||
- 160×160 (76,800 bytes) does NOT fit.
|
||||
- 96×192 (55,296 bytes) fits; padding may add a small overhead.
|
||||
- If the rendered image appears mirrored or upside‑down, rotate 90° the other way or flip horizontally in your editor and try again.
|
||||
- The path must be absolute.
|
||||
|
||||
### Requirements
|
||||
- 1D rotating LED strip/segment (POV setup). Ensure the segment length equals the number of physical LEDs.
|
||||
- BMP image saved as 24‑bit, uncompressed (no alpha, no palette).
|
||||
- Sufficient free RAM (~64 KB) for the image buffer.
|
||||
|
||||
### Troubleshooting
|
||||
- Nothing displays: verify the file exists at the exact absolute path (case‑sensitive) and is a 24‑bit uncompressed BMP.
|
||||
- Garbled colors or wrong orientation: re‑export as 24‑bit BMP and retry the rotation/flip guidance above.
|
||||
- Image too large: reduce width and/or height until it fits within ~64 KB (see examples).
|
||||
- Path issues: confirm you uploaded the file via the “/edit” URL and can see it in the filesystem browser.
|
||||
|
||||
### Safety
|
||||
- Secure the rotating assembly and keep clear of moving parts.
|
||||
- Balance the strip/hub to minimize vibration before running at speed.
|
||||
@@ -1,146 +0,0 @@
|
||||
#include "bmpimage.h"
|
||||
#define BUF_SIZE 64000
|
||||
|
||||
byte * _buffer = nullptr;
|
||||
|
||||
uint16_t read16(File &f) {
|
||||
uint16_t result;
|
||||
f.read((uint8_t *)&result,2);
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t read32(File &f) {
|
||||
uint32_t result;
|
||||
f.read((uint8_t *)&result,4);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool BMPimage::init(const char * fn) {
|
||||
File bmpFile;
|
||||
int bmpDepth;
|
||||
//first, check if filename exists
|
||||
if (!WLED_FS.exists(fn)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bmpFile = WLED_FS.open(fn);
|
||||
if (!bmpFile) {
|
||||
_valid=false;
|
||||
return false;
|
||||
}
|
||||
|
||||
//so, the file exists and is opened
|
||||
// Parse BMP header
|
||||
uint16_t header = read16(bmpFile);
|
||||
if(header != 0x4D42) { // BMP signature
|
||||
_valid=false;
|
||||
bmpFile.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
//read and ingnore file size
|
||||
read32(bmpFile);
|
||||
(void)read32(bmpFile); // Read & ignore creator bytes
|
||||
_imageOffset = read32(bmpFile); // Start of image data
|
||||
// Read DIB header
|
||||
read32(bmpFile);
|
||||
_width = read32(bmpFile);
|
||||
_height = read32(bmpFile);
|
||||
if(read16(bmpFile) != 1) { // # planes -- must be '1'
|
||||
_valid=false;
|
||||
bmpFile.close();
|
||||
return false;
|
||||
}
|
||||
bmpDepth = read16(bmpFile); // bits per pixel
|
||||
if((bmpDepth != 24) || (read32(bmpFile) != 0)) { // 0 = uncompressed {
|
||||
_width=0;
|
||||
_valid=false;
|
||||
bmpFile.close();
|
||||
return false;
|
||||
}
|
||||
// If _height is negative, image is in top-down order.
|
||||
// This is not canon but has been observed in the wild.
|
||||
if(_height < 0) {
|
||||
_height = -_height;
|
||||
}
|
||||
//now, we have successfully got all the basics
|
||||
// BMP rows are padded (if needed) to 4-byte boundary
|
||||
_rowSize = (_width * 3 + 3) & ~3;
|
||||
//check image size - if it is too large, it will be unusable
|
||||
if (_rowSize*_height>BUF_SIZE) {
|
||||
_valid=false;
|
||||
bmpFile.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
bmpFile.close();
|
||||
// Ensure filename fits our buffer (segment name length constraint).
|
||||
size_t len = strlen(fn);
|
||||
if (len > WLED_MAX_SEGNAME_LEN) {
|
||||
return false;
|
||||
}
|
||||
strncpy(filename, fn, sizeof(filename));
|
||||
filename[sizeof(filename) - 1] = '\0';
|
||||
_valid = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void BMPimage::clear(){
|
||||
strcpy(filename, "");
|
||||
_width=0;
|
||||
_height=0;
|
||||
_rowSize=0;
|
||||
_imageOffset=0;
|
||||
_loaded=false;
|
||||
_valid=false;
|
||||
}
|
||||
|
||||
bool BMPimage::load(){
|
||||
const size_t size = (size_t)_rowSize * (size_t)_height;
|
||||
if (size > BUF_SIZE) {
|
||||
return false;
|
||||
}
|
||||
File bmpFile = WLED_FS.open(filename);
|
||||
if (!bmpFile) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_buffer != nullptr) free(_buffer);
|
||||
_buffer = (byte*)malloc(size);
|
||||
if (_buffer == nullptr) return false;
|
||||
|
||||
bmpFile.seek(_imageOffset);
|
||||
const size_t readBytes = bmpFile.read(_buffer, size);
|
||||
bmpFile.close();
|
||||
if (readBytes != size) {
|
||||
_loaded = false;
|
||||
return false;
|
||||
}
|
||||
_loaded = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
byte* BMPimage::line(uint16_t n){
|
||||
if (_loaded) {
|
||||
return (_buffer+n*_rowSize);
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t BMPimage::pixelColor(uint16_t x, uint16_t y){
|
||||
uint32_t pos;
|
||||
byte b,g,r; //colors
|
||||
if (! _loaded) {
|
||||
return 0;
|
||||
}
|
||||
if ( (x>=_width) || (y>=_height) ) {
|
||||
return 0;
|
||||
}
|
||||
pos=y*_rowSize + 3*x;
|
||||
//get colors. Note that in BMP files, they go in BGR order
|
||||
b= _buffer[pos++];
|
||||
g= _buffer[pos++];
|
||||
r= _buffer[pos];
|
||||
return (r<<16|g<<8|b);
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
#ifndef _BMPIMAGE_H
|
||||
#define _BMPIMAGE_H
|
||||
#include "Arduino.h"
|
||||
#include "wled.h"
|
||||
|
||||
/*
|
||||
* This class describes a bitmap image. Each object refers to a bmp file on
|
||||
* filesystem fatfs.
|
||||
* To initialize, call init(), passign to it name of a bitmap file
|
||||
* at the root of fatfs filesystem:
|
||||
*
|
||||
* BMPimage myImage;
|
||||
* myImage.init("logo.bmp");
|
||||
*
|
||||
* For performance reasons, before actually usign the image, you need to load
|
||||
* it from filesystem to RAM:
|
||||
* myImage.load();
|
||||
* All load() operations use the same reserved buffer in RAM, so you can only
|
||||
* have one file loaded at a time. Before loading a new file, always unload the
|
||||
* previous one:
|
||||
* myImage.unload();
|
||||
*/
|
||||
|
||||
class BMPimage {
|
||||
public:
|
||||
int height() {return _height; }
|
||||
int width() {return _width; }
|
||||
int rowSize() {return _rowSize;}
|
||||
bool isLoaded() {return _loaded; }
|
||||
bool load();
|
||||
void unload() {_loaded=false; }
|
||||
byte * line(uint16_t n);
|
||||
uint32_t pixelColor(uint16_t x,uint16_t y);
|
||||
bool init(const char* fn);
|
||||
void clear();
|
||||
char * getFilename() {return filename;};
|
||||
|
||||
private:
|
||||
char filename[WLED_MAX_SEGNAME_LEN+1]="";
|
||||
int _width=0;
|
||||
int _height=0;
|
||||
int _rowSize=0;
|
||||
int _imageOffset=0;
|
||||
bool _loaded=false;
|
||||
bool _valid=false;
|
||||
};
|
||||
|
||||
extern byte * _buffer;
|
||||
|
||||
#endif
|
||||
@@ -1,5 +1,7 @@
|
||||
{
|
||||
"name:": "pov_display",
|
||||
"build": { "libArchive": false},
|
||||
"platforms": ["espressif32"]
|
||||
"dependencies": {
|
||||
"bitbank2/PNGdec":"^1.0.3"
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
#include "pov.h"
|
||||
|
||||
POV::POV() {}
|
||||
|
||||
void POV::showLine(const byte * line, uint16_t size){
|
||||
uint16_t i, pos;
|
||||
uint8_t r, g, b;
|
||||
if (!line) {
|
||||
// All-black frame on null input
|
||||
for (i = 0; i < SEGLEN; i++) {
|
||||
SEGMENT.setPixelColor(i, CRGB::Black);
|
||||
}
|
||||
strip.show();
|
||||
lastLineUpdate = micros();
|
||||
return;
|
||||
}
|
||||
for (i = 0; i < SEGLEN; i++) {
|
||||
if (i < size) {
|
||||
pos = 3 * i;
|
||||
// using bgr order
|
||||
b = line[pos++];
|
||||
g = line[pos++];
|
||||
r = line[pos];
|
||||
SEGMENT.setPixelColor(i, CRGB(r, g, b));
|
||||
} else {
|
||||
SEGMENT.setPixelColor(i, CRGB::Black);
|
||||
}
|
||||
}
|
||||
strip.show();
|
||||
lastLineUpdate = micros();
|
||||
}
|
||||
|
||||
bool POV::loadImage(const char * filename){
|
||||
if(!image.init(filename)) return false;
|
||||
if(!image.load()) return false;
|
||||
currentLine=0;
|
||||
return true;
|
||||
}
|
||||
|
||||
int16_t POV::showNextLine(){
|
||||
if (!image.isLoaded()) return 0;
|
||||
//move to next line
|
||||
showLine(image.line(currentLine), image.width());
|
||||
currentLine++;
|
||||
if (currentLine == image.height()) {currentLine=0;}
|
||||
return currentLine;
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
#ifndef _POV_H
|
||||
#define _POV_H
|
||||
#include "bmpimage.h"
|
||||
|
||||
|
||||
class POV {
|
||||
public:
|
||||
POV();
|
||||
|
||||
/* Shows one line. line should be pointer to array which holds pixel colors
|
||||
* (3 bytes per pixel, in BGR order). Note: 3, not 4!!!
|
||||
* size should be size of array (number of pixels, not number of bytes)
|
||||
*/
|
||||
void showLine(const byte * line, uint16_t size);
|
||||
|
||||
/* Reads from file an image and making it current image */
|
||||
bool loadImage(const char * filename);
|
||||
|
||||
/* Show next line of active image
|
||||
Retunrs the index of next line to be shown (not yet shown!)
|
||||
If it retunrs 0, it means we have completed showing the image and
|
||||
next call will start again
|
||||
*/
|
||||
int16_t showNextLine();
|
||||
|
||||
//time since strip was last updated, in micro sec
|
||||
uint32_t timeSinceUpdate() {return (micros()-lastLineUpdate);}
|
||||
|
||||
|
||||
BMPimage * currentImage() {return ℑ}
|
||||
|
||||
char * getFilename() {return image.getFilename();}
|
||||
|
||||
private:
|
||||
BMPimage image;
|
||||
int16_t currentLine=0; //next line to be shown
|
||||
uint32_t lastLineUpdate=0; //time in microseconds
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
@@ -1,75 +1,88 @@
|
||||
#include "wled.h"
|
||||
#include "pov.h"
|
||||
#include <PNGdec.h>
|
||||
|
||||
static const char _data_FX_MODE_POV_IMAGE[] PROGMEM = "POV Image@!;;;;";
|
||||
|
||||
static POV s_pov;
|
||||
|
||||
uint16_t mode_pov_image(void) {
|
||||
Segment& mainseg = strip.getMainSegment();
|
||||
const char* segName = mainseg.name;
|
||||
if (!segName) {
|
||||
return FRAMETIME;
|
||||
}
|
||||
// Only proceed for files ending with .bmp (case-insensitive)
|
||||
size_t segLen = strlen(segName);
|
||||
if (segLen < 4) return FRAMETIME;
|
||||
const char* ext = segName + (segLen - 4);
|
||||
// compare case-insensitive to ".bmp"
|
||||
if (!((ext[0]=='.') &&
|
||||
(ext[1]=='b' || ext[1]=='B') &&
|
||||
(ext[2]=='m' || ext[2]=='M') &&
|
||||
(ext[3]=='p' || ext[3]=='P'))) {
|
||||
return FRAMETIME;
|
||||
}
|
||||
|
||||
const char* current = s_pov.getFilename();
|
||||
if (current && strcmp(segName, current) == 0) {
|
||||
s_pov.showNextLine();
|
||||
return FRAMETIME;
|
||||
}
|
||||
|
||||
static unsigned long s_lastLoadAttemptMs = 0;
|
||||
unsigned long nowMs = millis();
|
||||
// Retry at most twice per second if the image is not yet loaded.
|
||||
if (nowMs - s_lastLoadAttemptMs < 500) return FRAMETIME;
|
||||
s_lastLoadAttemptMs = nowMs;
|
||||
s_pov.loadImage(segName);
|
||||
return FRAMETIME;
|
||||
void * openFile(const char *filename, int32_t *size) {
|
||||
f = WLED_FS.open(filename);
|
||||
*size = f.size();
|
||||
return &f;
|
||||
}
|
||||
|
||||
class PovDisplayUsermod : public Usermod {
|
||||
protected:
|
||||
bool enabled = false; //WLEDMM
|
||||
const char *_name; //WLEDMM
|
||||
bool initDone = false; //WLEDMM
|
||||
unsigned long lastTime = 0; //WLEDMM
|
||||
public:
|
||||
void closeFile(void *handle) {
|
||||
if (f) f.close();
|
||||
}
|
||||
|
||||
PovDisplayUsermod(const char *name, bool enabled)
|
||||
: enabled(enabled) , _name(name) {}
|
||||
|
||||
void setup() override {
|
||||
strip.addEffect(255, &mode_pov_image, _data_FX_MODE_POV_IMAGE);
|
||||
//initDone removed (unused)
|
||||
}
|
||||
int32_t readFile(PNGFILE *pFile, uint8_t *pBuf, int32_t iLen)
|
||||
{
|
||||
int32_t iBytesRead;
|
||||
iBytesRead = iLen;
|
||||
File *f = static_cast<File *>(pFile->fHandle);
|
||||
// Note: If you read a file all the way to the last byte, seek() stops working
|
||||
if ((pFile->iSize - pFile->iPos) < iLen)
|
||||
iBytesRead = pFile->iSize - pFile->iPos - 1; // <-- ugly work-around
|
||||
if (iBytesRead <= 0)
|
||||
return 0;
|
||||
iBytesRead = (int32_t)f->read(pBuf, iBytesRead);
|
||||
pFile->iPos = f->position();
|
||||
return iBytesRead;
|
||||
}
|
||||
|
||||
int32_t seekFile(PNGFILE *pFile, int32_t iPosition)
|
||||
{
|
||||
int i = micros();
|
||||
File *f = static_cast<File *>(pFile->fHandle);
|
||||
f->seek(iPosition);
|
||||
pFile->iPos = (int32_t)f->position();
|
||||
i = micros() - i;
|
||||
return pFile->iPos;
|
||||
}
|
||||
|
||||
void loop() override {
|
||||
// if usermod is disabled or called during strip updating just exit
|
||||
// NOTE: on very long strips strip.isUpdating() may always return true so update accordingly
|
||||
if (!enabled || strip.isUpdating()) return;
|
||||
|
||||
// do your magic here
|
||||
if (millis() - lastTime > 1000) {
|
||||
lastTime = millis();
|
||||
void draw(PNGDRAW *pDraw) {
|
||||
uint16_t usPixels[SEGLEN];
|
||||
png.getLineAsRGB565(pDraw, usPixels, PNG_RGB565_LITTLE_ENDIAN, 0xffffffff);
|
||||
for(int x=0; x < SEGLEN; x++) {
|
||||
uint16_t color = usPixels[x];
|
||||
byte r = ((color >> 11) & 0x1F);
|
||||
byte g = ((color >> 5) & 0x3F);
|
||||
byte b = (color & 0x1F);
|
||||
SEGMENT.setPixelColor(x, RGBW32(r,g,b,0));
|
||||
}
|
||||
}
|
||||
strip.show();
|
||||
}
|
||||
|
||||
uint16_t getId() override {
|
||||
return USERMOD_ID_POV_DISPLAY;
|
||||
}
|
||||
uint16_t mode_pov_image(void) {
|
||||
const char * filepath = SEGMENT.name;
|
||||
int rc = png.open(filepath, openFile, closeFile, readFile, seekFile, draw);
|
||||
if (rc == PNG_SUCCESS) {
|
||||
rc = png.decode(NULL, 0);
|
||||
png.close();
|
||||
return FRAMETIME;
|
||||
}
|
||||
return FRAMETIME;
|
||||
}
|
||||
|
||||
class PovDisplayUsermod : public Usermod
|
||||
{
|
||||
public:
|
||||
static const char _data_FX_MODE_POV_IMAGE[] PROGMEM = "POV Image@!;;;1";
|
||||
|
||||
PNG png;
|
||||
File f;
|
||||
|
||||
void setup() {
|
||||
strip.addEffect(255, &mode_pov_image, _data_FX_MODE_POV_IMAGE);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
}
|
||||
|
||||
uint16_t getId()
|
||||
{
|
||||
return USERMOD_ID_POV_DISPLAY;
|
||||
}
|
||||
|
||||
void connected() {}
|
||||
};
|
||||
|
||||
static PovDisplayUsermod pov_display("POV Display", false);
|
||||
REGISTER_USERMOD(pov_display);
|
||||
|
||||
static PovDisplayUsermod pov_display;
|
||||
REGISTER_USERMOD(pov_display);
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 988 KiB |
@@ -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);
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"name": "udp_name_sync",
|
||||
"build": { "libArchive": false },
|
||||
"dependencies": {}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
#include "wled.h"
|
||||
|
||||
class UdpNameSync : public Usermod {
|
||||
|
||||
private:
|
||||
|
||||
bool enabled = false;
|
||||
char segmentName[WLED_MAX_SEGNAME_LEN] = {0};
|
||||
static constexpr uint8_t kPacketType = 200; // custom usermod packet type
|
||||
static const char _name[];
|
||||
static const char _enabled[];
|
||||
|
||||
public:
|
||||
/**
|
||||
* Enable/Disable the usermod
|
||||
*/
|
||||
inline void enable(bool value) { enabled = value; }
|
||||
|
||||
/**
|
||||
* Get usermod enabled/disabled state
|
||||
*/
|
||||
inline bool isEnabled() const { return enabled; }
|
||||
|
||||
void setup() override {
|
||||
// Enabled when this usermod is compiled, set to false if you prefer runtime opt-in
|
||||
enable(true);
|
||||
}
|
||||
|
||||
void loop() override {
|
||||
if (!enabled) return;
|
||||
if (!WLED_CONNECTED) return;
|
||||
if (!udpConnected) return;
|
||||
Segment& mainseg = strip.getMainSegment();
|
||||
if (segmentName[0] == '\0' && !mainseg.name) return; //name was never set, do nothing
|
||||
|
||||
const char* curName = mainseg.name ? mainseg.name : "";
|
||||
if (strncmp(curName, segmentName, sizeof(segmentName)) == 0) return; // same name, do nothing
|
||||
|
||||
IPAddress broadcastIp = uint32_t(Network.localIP()) | ~uint32_t(Network.subnetMask());
|
||||
byte udpOut[WLED_MAX_SEGNAME_LEN + 2];
|
||||
udpOut[0] = kPacketType; // custom usermod packet type (avoid 0..5 used by core protocols)
|
||||
|
||||
if (segmentName[0] != '\0' && !mainseg.name) { // name cleared
|
||||
notifierUdp.beginPacket(broadcastIp, udpPort);
|
||||
segmentName[0] = '\0';
|
||||
DEBUG_PRINTLN(F("UdpNameSync: sending empty name"));
|
||||
udpOut[1] = 0; // explicit empty string
|
||||
notifierUdp.write(udpOut, 2);
|
||||
notifierUdp.endPacket();
|
||||
return;
|
||||
}
|
||||
|
||||
notifierUdp.beginPacket(broadcastIp, udpPort);
|
||||
DEBUG_PRINT(F("UdpNameSync: saving segment name "));
|
||||
DEBUG_PRINTLN(curName);
|
||||
strlcpy(segmentName, curName, sizeof(segmentName));
|
||||
strlcpy((char *)&udpOut[1], segmentName, sizeof(udpOut) - 1); // leave room for header byte
|
||||
size_t nameLen = strnlen((char *)&udpOut[1], sizeof(udpOut) - 1);
|
||||
notifierUdp.write(udpOut, 2 + nameLen);
|
||||
notifierUdp.endPacket();
|
||||
DEBUG_PRINT(F("UdpNameSync: Sent segment name : "));
|
||||
DEBUG_PRINTLN(segmentName);
|
||||
return;
|
||||
}
|
||||
|
||||
bool onUdpPacket(uint8_t * payload, size_t len) override {
|
||||
DEBUG_PRINT(F("UdpNameSync: Received packet"));
|
||||
if (!enabled) return false;
|
||||
if (receiveDirect) return false;
|
||||
if (len < 2) return false; // need type + at least 1 byte for name (can be 0)
|
||||
if (payload[0] != kPacketType) return false;
|
||||
Segment& mainseg = strip.getMainSegment();
|
||||
char tmp[WLED_MAX_SEGNAME_LEN] = {0};
|
||||
size_t copyLen = len - 1;
|
||||
if (copyLen > sizeof(tmp) - 1) copyLen = sizeof(tmp) - 1;
|
||||
memcpy(tmp, &payload[1], copyLen);
|
||||
tmp[copyLen] = '\0';
|
||||
mainseg.setName(tmp);
|
||||
DEBUG_PRINT(F("UdpNameSync: set segment name"));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
static UdpNameSync udp_name_sync;
|
||||
REGISTER_USERMOD(udp_name_sync);
|
||||
@@ -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 1‑D; `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 0–255 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 0–255) 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. (It’s 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 (0–255 range, usually exposed via sliders) to define the diffusion rate, mapped to 0–100.
|
||||
* This controls how much "heat" spreads to neighboring pixels — more diffusion = smoother flame spread.
|
||||
* Next we assign `SEGMENT.intensity` (user input 0–255) 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 can’t 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 variable‑length arrays (VLAs) is non‑standard 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. It’s 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 0–255 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` (0–255).
|
||||
* 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 there’s a pixel to the left, add its heat to the total.
|
||||
* If there’s 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 doesn’t 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, let’s 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, there’s 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 FastLED’s 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 doesn’t 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.
|
||||
|
||||
@@ -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]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
331
wled00/FX.cpp
331
wled00/FX.cpp
@@ -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;
|
||||
@@ -2329,7 +2328,7 @@ uint16_t mode_colortwinkle() {
|
||||
}
|
||||
|
||||
if (cur == prev) { //fix "stuck" pixels
|
||||
col = color_add(col, col);
|
||||
color_add(col, col);
|
||||
SEGMENT.setPixelColor(i, col);
|
||||
}
|
||||
else SEGMENT.setPixelColor(i, col);
|
||||
@@ -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()
|
||||
@@ -3943,7 +3940,7 @@ uint16_t mode_percent(void) {
|
||||
|
||||
return FRAMETIME;
|
||||
}
|
||||
static const char _data_FX_MODE_PERCENT[] PROGMEM = "Percent@!,% of fill,,,,One color;!,!;!";
|
||||
static const char _data_FX_MODE_PERCENT[] PROGMEM = "Percent@,% of fill,,,,One color;!,!;!";
|
||||
|
||||
|
||||
/*
|
||||
@@ -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
|
||||
|
||||
|
||||
/////////////////////////
|
||||
@@ -7660,9 +7528,9 @@ uint16_t mode_2Ddistortionwaves() {
|
||||
byte valueG = gdistort + ((a2-( ((xoffs - cx1) * (xoffs - cx1) + (yoffs - cy1) * (yoffs - cy1))>>7 ))<<1);
|
||||
byte valueB = bdistort + ((a3-( ((xoffs - cx2) * (xoffs - cx2) + (yoffs - cy2) * (yoffs - cy2))>>7 ))<<1);
|
||||
|
||||
valueR = cos8_t(valueR);
|
||||
valueG = cos8_t(valueG);
|
||||
valueB = cos8_t(valueB);
|
||||
valueR = gamma8(cos8_t(valueR));
|
||||
valueG = gamma8(cos8_t(valueG));
|
||||
valueB = gamma8(cos8_t(valueB));
|
||||
|
||||
if(SEGMENT.palette == 0) {
|
||||
// use RGB values (original color mode)
|
||||
@@ -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
|
||||
|
||||
37
wled00/FX.h
37
wled00/FX.h
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
297
wled00/FX_fcn.cpp
Normal file → Executable file
297
wled00/FX_fcn.cpp
Normal file → Executable file
@@ -11,6 +11,7 @@
|
||||
*/
|
||||
#include "wled.h"
|
||||
#include "FXparticleSystem.h" // TODO: better define the required function (mem service) in FX.h?
|
||||
#include "palettes.h"
|
||||
|
||||
/*
|
||||
Custom per-LED mapping has moved!
|
||||
@@ -67,10 +68,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 +97,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 +111,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 +130,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 +147,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 +209,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 +219,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 +256,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;
|
||||
@@ -292,7 +282,6 @@ void Segment::startTransition(uint16_t dur, bool segmentCopy) {
|
||||
_t->_oldSegment = new(std::nothrow) Segment(*this); // store/copy current segment settings
|
||||
_t->_start = millis(); // restart countdown
|
||||
_t->_dur = dur;
|
||||
_t->_prevPaletteBlends = 0;
|
||||
if (_t->_oldSegment) {
|
||||
_t->_oldSegment->palette = _t->_palette; // restore original palette and colors (from start of transition)
|
||||
for (unsigned i = 0; i < NUM_COLORS; i++) _t->_oldSegment->colors[i] = _t->_colors[i];
|
||||
@@ -379,7 +368,6 @@ void Segment::beginDraw(uint16_t prog) {
|
||||
// minimum blend time is 100ms maximum is 65535ms
|
||||
#ifndef WLED_SAVE_RAM
|
||||
unsigned noOfBlends = ((255U * prog) / 0xFFFFU) - _t->_prevPaletteBlends;
|
||||
if(noOfBlends > 255) noOfBlends = 255; // safety check
|
||||
for (unsigned i = 0; i < noOfBlends; i++, _t->_prevPaletteBlends++) nblendPaletteTowardPalette(_t->_palT, Segment::_currentPalette, 48);
|
||||
Segment::_currentPalette = _t->_palT; // copy transitioning/temporary palette
|
||||
#else
|
||||
@@ -448,9 +436,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 +454,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 +464,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 +564,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 +579,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 +671,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 +689,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 +902,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 +1041,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,32 +1061,51 @@ 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);
|
||||
}
|
||||
|
||||
/*
|
||||
* Put a value 0 to 255 in to get a color value.
|
||||
* The colours are a transition r -> g -> b -> back to r
|
||||
* Rotates the color in HSV space, where pos is H. (0=0deg, 256=360deg)
|
||||
* Inspired by the Adafruit examples.
|
||||
*/
|
||||
uint32_t Segment::color_wheel(uint8_t pos) const {
|
||||
if (palette) return color_from_palette(pos, false, false, 0); // only wrap if "always wrap" is set
|
||||
if (palette) return color_from_palette(pos, false, false, 0); // never wrap palette
|
||||
uint8_t w = W(getCurrentColor(0));
|
||||
uint32_t rgb;
|
||||
hsv2rgb(CHSV32(static_cast<uint16_t>(pos << 8), 255, 255), rgb);
|
||||
return rgb | (w << 24); // add white channel
|
||||
pos = 255 - pos;
|
||||
if (useRainbowWheel) {
|
||||
CRGB rgb;
|
||||
hsv2rgb_rainbow(CHSV(pos, 255, 255), rgb);
|
||||
return RGBW32(rgb.r, rgb.g, rgb.b, w);
|
||||
} else {
|
||||
if (pos < 85) {
|
||||
return RGBW32((255 - pos * 3), 0, (pos * 3), w);
|
||||
} else if (pos < 170) {
|
||||
pos -= 85;
|
||||
return RGBW32(0, (pos * 3), (255 - pos * 3), w);
|
||||
} else {
|
||||
pos -= 170;
|
||||
return RGBW32((pos * 3), (255 - pos * 3), 0, w);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1183,42 +1182,14 @@ 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;
|
||||
}
|
||||
mem += bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount++ : 0); // includes global buffer
|
||||
if (mem <= MAX_LED_MEMORY) {
|
||||
if (BusManager::add(bus) == -1) break;
|
||||
} else DEBUG_PRINTF_P(PSTR("Out of LED memory! Bus %d (%d) #%u not created."), (int)bus.type, (int)bus.count, digitalCount);
|
||||
}
|
||||
DEBUG_PRINTF_P(PSTR("LED buffer size: %uB/%uB\n"), mem + maxI2S, BusManager::memUsage());
|
||||
busConfigs.clear();
|
||||
busConfigs.shrink_to_fit();
|
||||
|
||||
@@ -1234,9 +1205,8 @@ void WS2812FX::finalizeInit() {
|
||||
if (busEnd > _length) _length = busEnd;
|
||||
// This must be done after all buses have been created, as some kinds (parallel I2S) interact
|
||||
bus->begin();
|
||||
bus->setBrightness(scaledBri(bri));
|
||||
bus->setBrightness(bri);
|
||||
}
|
||||
BusManager::initializeABL(); // init brightness limiter
|
||||
DEBUG_PRINTF_P(PSTR("Heap after buses: %d\n"), ESP.getFreeHeap());
|
||||
|
||||
Segment::maxWidth = _length;
|
||||
@@ -1249,11 +1219,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() {
|
||||
@@ -1339,7 +1308,7 @@ static uint8_t _add (uint8_t a, uint8_t b) { unsigned t = a + b; return t
|
||||
static uint8_t _subtract (uint8_t a, uint8_t b) { return b > a ? (b - a) : 0; }
|
||||
static uint8_t _difference(uint8_t a, uint8_t b) { return b > a ? (b - a) : (a - b); }
|
||||
static uint8_t _average (uint8_t a, uint8_t b) { return (a + b) >> 1; }
|
||||
#if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32C3
|
||||
static uint8_t _multiply (uint8_t a, uint8_t b) { return ((a * b) + 255) >> 8; } // faster than division on C3 but slightly less accurate
|
||||
#else
|
||||
static uint8_t _multiply (uint8_t a, uint8_t b) { return (a * b) / 255; } // origianl uses a & b in range [0,1]
|
||||
@@ -1350,10 +1319,10 @@ static uint8_t _darken (uint8_t a, uint8_t b) { return a < b ? a : b; }
|
||||
static uint8_t _screen (uint8_t a, uint8_t b) { return 255 - _multiply(~a,~b); } // 255 - (255-a)*(255-b)/255
|
||||
static uint8_t _overlay (uint8_t a, uint8_t b) { return b < 128 ? 2 * _multiply(a,b) : (255 - 2 * _multiply(~a,~b)); }
|
||||
static uint8_t _hardlight (uint8_t a, uint8_t b) { return a < 128 ? 2 * _multiply(a,b) : (255 - 2 * _multiply(~a,~b)); }
|
||||
#if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
static uint8_t _softlight (uint8_t a, uint8_t b) { return (((b * b * (255 - 2 * a))) + ((2 * a * b + 256) << 8)) >> 16; } // Pegtop's formula (1 - 2a)b^2
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32C3
|
||||
static uint8_t _softlight (uint8_t a, uint8_t b) { return (((b * b * (255 - 2 * a) + 255) >> 8) + 2 * a * b + 255) >> 8; } // Pegtop's formula (1 - 2a)b^2 + 2ab
|
||||
#else
|
||||
static uint8_t _softlight (uint8_t a, uint8_t b) { return (b * b * (255 - 2 * a) + 255 * 2 * a * b) / (255 * 255); } // Pegtop's formula (1 - 2a)b^2 + 2ab
|
||||
static uint8_t _softlight (uint8_t a, uint8_t b) { return (b * b * (255 - 2 * a) / 255 + 2 * a * b) / 255; } // Pegtop's formula (1 - 2a)b^2 + 2ab
|
||||
#endif
|
||||
static uint8_t _dodge (uint8_t a, uint8_t b) { return _divide(~a,b); }
|
||||
static uint8_t _burn (uint8_t a, uint8_t b) { return ~_divide(a,~b); }
|
||||
@@ -1384,6 +1353,11 @@ void WS2812FX::blendSegment(const Segment &topSegment) const {
|
||||
uint8_t opacity = topSegment.currentBri(); // returns transitioned opacity for style FADE
|
||||
uint8_t cct = topSegment.currentCCT();
|
||||
|
||||
if (length == 1) {
|
||||
// Can't blend only a single pixel, prevents crash when bus init fails
|
||||
return;
|
||||
}
|
||||
|
||||
Segment::setClippingRect(0, 0); // disable clipping by default
|
||||
|
||||
const unsigned dw = (blendingStyle==BLEND_STYLE_OUTSIDE_IN ? progInv : progress) * width / 0xFFFFU + 1;
|
||||
@@ -1592,12 +1566,68 @@ void WS2812FX::blendSegment(const Segment &topSegment) const {
|
||||
Segment::setClippingRect(0, 0); // disable clipping for overlays
|
||||
}
|
||||
|
||||
void WS2812FX::show() {
|
||||
if (!_pixels) {
|
||||
DEBUGFX_PRINTLN(F("Error: no _pixels!"));
|
||||
errorFlag = ERR_NORAM;
|
||||
return; // no pixels allocated, nothing to show
|
||||
// To disable brightness limiter we either set output max current to 0 or single LED current to 0
|
||||
static uint8_t estimateCurrentAndLimitBri(uint8_t brightness, uint32_t *pixels) {
|
||||
unsigned milliAmpsMax = BusManager::ablMilliampsMax();
|
||||
if (milliAmpsMax > 0) {
|
||||
unsigned milliAmpsTotal = 0;
|
||||
unsigned avgMilliAmpsPerLED = 0;
|
||||
unsigned lengthDigital = 0;
|
||||
bool useWackyWS2815PowerModel = false;
|
||||
|
||||
for (size_t i = 0; i < BusManager::getNumBusses(); i++) {
|
||||
const Bus *bus = BusManager::getBus(i);
|
||||
if (!(bus && bus->isDigital() && bus->isOk())) continue;
|
||||
unsigned maPL = bus->getLEDCurrent();
|
||||
if (maPL == 0 || bus->getMaxCurrent() > 0) continue; // skip buses with 0 mA per LED or max current per bus defined (PP-ABL)
|
||||
if (maPL == 255) {
|
||||
useWackyWS2815PowerModel = true;
|
||||
maPL = 12; // WS2815 uses 12mA per channel
|
||||
}
|
||||
avgMilliAmpsPerLED += maPL * bus->getLength();
|
||||
lengthDigital += bus->getLength();
|
||||
// sum up the usage of each LED on digital bus
|
||||
uint32_t busPowerSum = 0;
|
||||
for (unsigned j = 0; j < bus->getLength(); j++) {
|
||||
uint32_t c = pixels[j + bus->getStart()];
|
||||
byte r = R(c), g = G(c), b = B(c), w = W(c);
|
||||
if (useWackyWS2815PowerModel) { //ignore white component on WS2815 power calculation
|
||||
busPowerSum += (max(max(r,g),b)) * 3;
|
||||
} else {
|
||||
busPowerSum += (r + g + b + w);
|
||||
}
|
||||
}
|
||||
// RGBW led total output with white LEDs enabled is still 50mA, so each channel uses less
|
||||
if (bus->hasWhite()) {
|
||||
busPowerSum *= 3;
|
||||
busPowerSum >>= 2; //same as /= 4
|
||||
}
|
||||
// powerSum has all the values of channels summed (max would be getLength()*765 as white is excluded) so convert to milliAmps
|
||||
milliAmpsTotal += (busPowerSum * maPL * brightness) / (765*255);
|
||||
}
|
||||
if (lengthDigital > 0) {
|
||||
avgMilliAmpsPerLED /= lengthDigital;
|
||||
|
||||
if (milliAmpsMax > MA_FOR_ESP && avgMilliAmpsPerLED > 0) { //0 mA per LED and too low numbers turn off calculation
|
||||
unsigned powerBudget = (milliAmpsMax - MA_FOR_ESP); //80/120mA for ESP power
|
||||
if (powerBudget > lengthDigital) { //each LED uses about 1mA in standby, exclude that from power budget
|
||||
powerBudget -= lengthDigital;
|
||||
} else {
|
||||
powerBudget = 0;
|
||||
}
|
||||
if (milliAmpsTotal > powerBudget) {
|
||||
//scale brightness down to stay in current limit
|
||||
unsigned scaleB = powerBudget * 255 / milliAmpsTotal;
|
||||
brightness = ((brightness * scaleB) >> 8) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return brightness;
|
||||
}
|
||||
|
||||
void WS2812FX::show() {
|
||||
if (!_pixels) return; // no pixels allocated, nothing to show
|
||||
|
||||
unsigned long showNow = millis();
|
||||
size_t diff = showNow - _lastShow;
|
||||
@@ -1607,7 +1637,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) {
|
||||
@@ -1623,6 +1653,10 @@ void WS2812FX::show() {
|
||||
show_callback callback = _callback;
|
||||
if (callback) callback(); // will call setPixelColor or setRealtimePixelColor
|
||||
|
||||
// determine ABL brightness
|
||||
uint8_t newBri = estimateCurrentAndLimitBri(_brightness, _pixels);
|
||||
if (newBri != _brightness) BusManager::setBrightness(newBri);
|
||||
|
||||
// paint actual pixels
|
||||
int oldCCT = Bus::getCCT(); // store original CCT value (since it is global)
|
||||
// when cctFromRgb is true we implicitly calculate WW and CW from RGB values (cct==-1)
|
||||
@@ -1633,15 +1667,11 @@ void WS2812FX::show() {
|
||||
if (_pixelCCT) { // cctFromRgb already exluded at allocation
|
||||
if (i == 0 || _pixelCCT[i-1] != _pixelCCT[i]) BusManager::setSegmentCCT(_pixelCCT[i], correctWB);
|
||||
}
|
||||
|
||||
uint32_t c = _pixels[i]; // need a copy, do not modify _pixels directly (no byte access allowed on ESP32)
|
||||
if(c > 0 && !(realtimeMode && arlsDisableGammaCorrection))
|
||||
c = gamma32(c); // apply gamma correction if enabled note: applying gamma after brightness has too much color loss
|
||||
BusManager::setPixelColor(getMappedPixelIndex(i), c);
|
||||
BusManager::setPixelColor(getMappedPixelIndex(i), realtimeMode && arlsDisableGammaCorrection ? _pixels[i] : gamma32(_pixels[i]));
|
||||
}
|
||||
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
|
||||
@@ -1649,6 +1679,9 @@ void WS2812FX::show() {
|
||||
// See https://github.com/Makuna/NeoPixelBus/wiki/ESP32-NeoMethods#neoesp32rmt-methods
|
||||
BusManager::show();
|
||||
|
||||
// restore brightness for next frame
|
||||
if (newBri != _brightness) BusManager::setBrightness(_brightness);
|
||||
|
||||
if (diff > 0) { // skip calculation if no time has passed
|
||||
size_t fpsCurr = (1000 << FPS_CALC_SHIFT) / diff; // fixed point math
|
||||
_cumulativeFps = (FPS_CALC_AVG * _cumulativeFps + fpsCurr + FPS_CALC_AVG / 2) / (FPS_CALC_AVG + 1); // "+FPS_CALC_AVG/2" for proper rounding
|
||||
@@ -1713,7 +1746,7 @@ void WS2812FX::setBrightness(uint8_t b, bool direct) {
|
||||
if (_brightness == 0) { //unfreeze all segments on power off
|
||||
for (const Segment &seg : _segments) seg.freeze = false; // freeze is mutable
|
||||
}
|
||||
BusManager::setBrightness(scaledBri(b));
|
||||
BusManager::setBrightness(b);
|
||||
if (!direct) {
|
||||
unsigned long t = millis();
|
||||
if (_segments[0].next_time > t + 22 && t - _lastShow > MIN_SHOW_DELAY) trigger(); //apply brightness change immediately if no refresh soon
|
||||
|
||||
@@ -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
|
||||
@@ -1115,7 +1118,7 @@ bool initParticleSystem2D(ParticleSystem2D *&PartSys, uint32_t requestedsources,
|
||||
allocsuccess = true;
|
||||
break; // allocation succeeded
|
||||
}
|
||||
numparticles = ((numparticles / 2) + 3) & ~0x03; // cut number of particles in half and try again, must be 4 byte aligned
|
||||
numparticles /= 2; // cut number of particles in half and try again
|
||||
PSPRINTLN(F("PS 2D alloc failed, trying with less particles..."));
|
||||
}
|
||||
if (!allocsuccess) {
|
||||
@@ -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
|
||||
|
||||
@@ -1812,7 +1815,7 @@ bool initParticleSystem1D(ParticleSystem1D *&PartSys, const uint32_t requestedso
|
||||
allocsuccess = true;
|
||||
break; // allocation succeeded
|
||||
}
|
||||
numparticles = ((numparticles / 2) + 3) & ~0x03; // cut number of particles in half and try again, must be 4 byte aligned
|
||||
numparticles /= 2; // cut number of particles in half and try again
|
||||
PSPRINTLN(F("PS 1D alloc failed, trying with less particles..."));
|
||||
}
|
||||
if (!allocsuccess) {
|
||||
@@ -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))
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
#include "core_esp8266_waveform.h"
|
||||
#endif
|
||||
#include "const.h"
|
||||
#include "colors.h"
|
||||
#include "pin_manager.h"
|
||||
#include "bus_manager.h"
|
||||
#include "bus_wrapper.h"
|
||||
@@ -32,36 +31,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 +38,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))))
|
||||
@@ -169,7 +144,6 @@ BusDigital::BusDigital(const BusConfig &bc, uint8_t nr)
|
||||
if (!isDigital(bc.type) || !bc.count) { DEBUGBUS_PRINTLN(F("Not digial or empty bus!")); return; }
|
||||
if (!PinManager::allocatePin(bc.pins[0], true, PinOwner::BusDigital)) { DEBUGBUS_PRINTLN(F("Pin 0 allocated!")); return; }
|
||||
_frequencykHz = 0U;
|
||||
_colorSum = 0;
|
||||
_pins[0] = bc.pins[0];
|
||||
if (is2Pin(bc.type)) {
|
||||
if (!PinManager::allocatePin(bc.pins[1], true, PinOwner::BusDigital)) {
|
||||
@@ -212,65 +186,80 @@ BusDigital::BusDigital(const BusConfig &bc, uint8_t nr)
|
||||
//Stay safe with high amperage and have a reasonable safety margin!
|
||||
//I am NOT to be held liable for burned down garages or houses!
|
||||
|
||||
// note on ABL implementation:
|
||||
// ABL is set up in finalizeInit()
|
||||
// scaled color channels are summed in BusDigital::setPixelColor()
|
||||
// the used current is estimated and limited in BusManager::show()
|
||||
// if limit is set too low, brightness is limited to 1 to at least show some light
|
||||
// to disable brightness limiter for a bus, set LED current to 0
|
||||
// To disable brightness limiter we either set output max current to 0 or single LED current to 0
|
||||
uint8_t BusDigital::estimateCurrentAndLimitBri() const {
|
||||
bool useWackyWS2815PowerModel = false;
|
||||
byte actualMilliampsPerLed = _milliAmpsPerLed;
|
||||
|
||||
if (_milliAmpsMax < MA_FOR_ESP/BusManager::getNumBusses() || actualMilliampsPerLed == 0) { //0 mA per LED and too low numbers turn off calculation
|
||||
return _bri;
|
||||
}
|
||||
|
||||
void BusDigital::estimateCurrent() {
|
||||
uint32_t actualMilliampsPerLed = _milliAmpsPerLed;
|
||||
if (_milliAmpsPerLed == 255) {
|
||||
// use wacky WS2815 power model, see WLED issue #549
|
||||
_colorSum *= 3; // sum is sum of max value for each color, need to multiply by three to account for clrUnitsPerChannel being 3*255
|
||||
useWackyWS2815PowerModel = true;
|
||||
actualMilliampsPerLed = 12; // from testing an actual strip
|
||||
}
|
||||
// _colorSum has all the values of color channels summed, max would be getLength()*(3*255 + (255 if hasWhite()): convert to milliAmps
|
||||
uint32_t clrUnitsPerChannel = hasWhite() ? 4*255 : 3*255;
|
||||
_milliAmpsTotal = ((uint64_t)_colorSum * actualMilliampsPerLed) / clrUnitsPerChannel + getLength(); // add 1mA standby current per LED to total (WS2812: ~0.7mA, WS2815: ~2mA)
|
||||
}
|
||||
|
||||
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;
|
||||
unsigned powerBudget = (_milliAmpsMax - MA_FOR_ESP/BusManager::getNumBusses()); //80/120mA for ESP power
|
||||
if (powerBudget > getLength()) { //each LED uses about 1mA in standby, exclude that from power budget
|
||||
powerBudget -= getLength();
|
||||
} else {
|
||||
powerBudget = 0;
|
||||
}
|
||||
|
||||
if (_milliAmpsLimit > getLength()) { // each LED uses about 1mA in standby
|
||||
if (_milliAmpsTotal > _milliAmpsLimit) {
|
||||
// scale brightness down to stay in current limit
|
||||
newBri = ((uint32_t)_milliAmpsLimit * 255) / _milliAmpsTotal + 1; // +1 to avoid 0 brightness
|
||||
_milliAmpsTotal = _milliAmpsLimit;
|
||||
}
|
||||
uint32_t busPowerSum = 0;
|
||||
for (unsigned i = 0; i < getLength(); i++) { //sum up the usage of each LED
|
||||
uint32_t c = getPixelColor(i); // always returns original or restored color without brightness scaling
|
||||
byte r = R(c), g = G(c), b = B(c), w = W(c);
|
||||
|
||||
if (useWackyWS2815PowerModel) { //ignore white component on WS2815 power calculation
|
||||
busPowerSum += (max(max(r,g),b)) * 3;
|
||||
} else {
|
||||
newBri = 1; // limit too low, set brightness to 1, this will dim down all colors to minimum since we use video scaling
|
||||
_milliAmpsTotal = getLength(); // estimate bus current as minimum
|
||||
busPowerSum += (r + g + b + w);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
for (unsigned i = 0; i < hwLen; i++) {
|
||||
uint8_t co = _colorOrderMap.getPixelColorOrder(i+_start, _colorOrder); // need to revert color order for correct color scaling and CCT calc in case white is swapped
|
||||
uint32_t c = PolyBus::getPixelColor(_busPtr, _iType, i, co);
|
||||
c = color_fade(c, newBri, true); // apply additional dimming note: using inline version is a bit faster but overhead of getPixelColor() dominates the speed impact by far
|
||||
if (hasCCT()) Bus::calculateCCT(c, cctWW, cctCW);
|
||||
PolyBus::setPixelColor(_busPtr, _iType, i, c, co, (cctCW<<8) | cctWW); // repaint all pixels with new brightness
|
||||
}
|
||||
if (hasWhite()) { //RGBW led total output with white LEDs enabled is still 50mA, so each channel uses less
|
||||
busPowerSum *= 3;
|
||||
busPowerSum >>= 2; //same as /= 4
|
||||
}
|
||||
|
||||
_colorSum = 0; // reset for next frame
|
||||
// powerSum has all the values of channels summed (max would be getLength()*765 as white is excluded) so convert to milliAmps
|
||||
BusDigital::_milliAmpsTotal = (busPowerSum * actualMilliampsPerLed * _bri) / (765*255);
|
||||
|
||||
uint8_t newBri = _bri;
|
||||
if (BusDigital::_milliAmpsTotal > powerBudget) {
|
||||
//scale brightness down to stay in current limit
|
||||
unsigned scaleB = powerBudget * 255 / BusDigital::_milliAmpsTotal;
|
||||
newBri = (_bri * scaleB) / 256 + 1;
|
||||
BusDigital::_milliAmpsTotal = powerBudget;
|
||||
//_milliAmpsTotal = (busPowerSum * actualMilliampsPerLed * newBri) / (765*255);
|
||||
}
|
||||
return newBri;
|
||||
}
|
||||
|
||||
void BusDigital::show() {
|
||||
BusDigital::_milliAmpsTotal = 0;
|
||||
if (!_valid) return;
|
||||
_NPBbri = (_NPBbri * _bri) / 255; // total applied brightness for use in restoreColorLossy (see applyBriLimit())
|
||||
|
||||
uint8_t cctWW = 0, cctCW = 0;
|
||||
unsigned newBri = estimateCurrentAndLimitBri(); // will fill _milliAmpsTotal (TODO: could use PolyBus::CalcTotalMilliAmpere())
|
||||
if (newBri < _bri) {
|
||||
PolyBus::setBrightness(_busPtr, _iType, newBri); // limit brightness to stay within current limits
|
||||
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
|
||||
for (unsigned i = 0; i < hwLen; i++) {
|
||||
// use 0 as color order, actual order does not matter here as we just update the channel values as-is
|
||||
uint32_t c = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, i, 0), _bri);
|
||||
if (hasCCT()) Bus::calculateCCT(c, cctWW, cctCW); // this will unfortunately corrupt (segment) CCT data on every bus
|
||||
PolyBus::setPixelColor(_busPtr, _iType, i, c, 0, (cctCW<<8) | cctWW); // repaint all pixels with new brightness
|
||||
}
|
||||
}
|
||||
PolyBus::show(_busPtr, _iType, _skip); // faster if buffer consistency is not important (no skipped LEDs)
|
||||
// restore bus brightness to its original value
|
||||
// this is done right after show, so this is only OK if LED updates are completed before show() returns
|
||||
// or async show has a separate buffer (ESP32 RMT and I2S are ok)
|
||||
if (newBri < _bri) PolyBus::setBrightness(_busPtr, _iType, _bri);
|
||||
}
|
||||
|
||||
bool BusDigital::canShow() const {
|
||||
@@ -278,6 +267,12 @@ bool BusDigital::canShow() const {
|
||||
return PolyBus::canShow(_busPtr, _iType);
|
||||
}
|
||||
|
||||
void BusDigital::setBrightness(uint8_t b) {
|
||||
if (_bri == b) return;
|
||||
Bus::setBrightness(b);
|
||||
PolyBus::setBrightness(_busPtr, _iType, b);
|
||||
}
|
||||
|
||||
//If LEDs are skipped, it is possible to use the first as a status LED.
|
||||
//TODO only show if no new show due in the next 50ms
|
||||
void BusDigital::setStatusPixel(uint32_t c) {
|
||||
@@ -291,25 +286,13 @@ void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) {
|
||||
if (!_valid) return;
|
||||
if (hasWhite()) c = autoWhiteCalc(c);
|
||||
if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT
|
||||
c = color_fade(c, _bri, true); // apply brightness
|
||||
|
||||
if (BusManager::_useABL) {
|
||||
// if using ABL, sum all color channels to estimate current and limit brightness in show()
|
||||
uint8_t r = R(c), g = G(c), b = B(c);
|
||||
if (_milliAmpsPerLed < 255) { // normal ABL
|
||||
_colorSum += r + g + b + W(c);
|
||||
} else { // wacky WS2815 power model, ignore white channel, use max of RGB (issue #549)
|
||||
_colorSum += ((r > g) ? ((r > b) ? r : b) : ((g > b) ? g : b));
|
||||
}
|
||||
}
|
||||
|
||||
if (_reversed) pix = _len - pix -1;
|
||||
pix += _skip;
|
||||
const uint8_t co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder);
|
||||
unsigned co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder);
|
||||
if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs
|
||||
unsigned pOld = pix;
|
||||
pix = IC_INDEX_WS2812_1CH_3X(pix);
|
||||
uint32_t cOld = PolyBus::getPixelColor(_busPtr, _iType, pix, co);
|
||||
uint32_t cOld = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, pix, co),_bri);
|
||||
switch (pOld % 3) { // change only the single channel (TODO: this can cause loss because of get/set)
|
||||
case 0: c = RGBW32(R(cOld), W(c) , B(cOld), 0); break;
|
||||
case 1: c = RGBW32(W(c) , G(cOld), B(cOld), 0); break;
|
||||
@@ -326,17 +309,17 @@ void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) {
|
||||
PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, wwcw);
|
||||
}
|
||||
|
||||
// returns lossly restored color from bus
|
||||
// returns original color if global buffering is enabled, else returns lossly restored color from bus
|
||||
uint32_t IRAM_ATTR BusDigital::getPixelColor(unsigned pix) const {
|
||||
if (!_valid) return 0;
|
||||
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);
|
||||
const unsigned co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder);
|
||||
uint32_t c = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, (_type==TYPE_WS2812_1CH_X3) ? IC_INDEX_WS2812_1CH_3X(pix) : pix, co),_bri);
|
||||
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?
|
||||
uint8_t b = _reversed ? G(c) : B(c);
|
||||
unsigned r = R(c);
|
||||
unsigned g = _reversed ? B(c) : G(c); // should G and B be switched if _reversed?
|
||||
unsigned b = _reversed ? G(c) : B(c);
|
||||
switch (pix % 3) { // get only the single channel
|
||||
case 0: c = RGBW32(g, g, g, g); break;
|
||||
case 1: c = RGBW32(r, r, r, r); break;
|
||||
@@ -357,7 +340,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) {
|
||||
@@ -488,7 +471,10 @@ void BusPwm::setPixelColor(unsigned pix, uint32_t c) {
|
||||
if (Bus::_cct >= 1900 && (_type == TYPE_ANALOG_3CH || _type == TYPE_ANALOG_4CH)) {
|
||||
c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT
|
||||
}
|
||||
uint8_t r = R(c), g = G(c), b = B(c), w = W(c);
|
||||
uint8_t r = R(c);
|
||||
uint8_t g = G(c);
|
||||
uint8_t b = B(c);
|
||||
uint8_t w = W(c);
|
||||
|
||||
switch (_type) {
|
||||
case TYPE_ANALOG_1CH: //one channel (white), relies on auto white calculation
|
||||
@@ -663,7 +649,10 @@ BusOnOff::BusOnOff(const BusConfig &bc)
|
||||
void BusOnOff::setPixelColor(unsigned pix, uint32_t c) {
|
||||
if (pix != 0 || !_valid) return; //only react to first pixel
|
||||
c = autoWhiteCalc(c);
|
||||
uint8_t r = R(c), g = G(c), b = B(c), w = W(c);
|
||||
uint8_t r = R(c);
|
||||
uint8_t g = G(c);
|
||||
uint8_t b = B(c);
|
||||
uint8_t w = W(c);
|
||||
_data = bool(r|g|b|w) && bool(_bri) ? 0xFF : 0;
|
||||
}
|
||||
|
||||
@@ -787,347 +776,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 +798,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 +832,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 +863,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 +918,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
|
||||
@@ -1317,13 +964,13 @@ void BusManager::off() {
|
||||
#ifdef ESP32_DATA_IDLE_HIGH
|
||||
esp32RMTInvertIdle();
|
||||
#endif
|
||||
_gMilliAmpsUsed = 0; // reset, assume no LED idle current if relay is off
|
||||
}
|
||||
|
||||
void BusManager::show() {
|
||||
applyABL(); // apply brightness limit, updates _gMilliAmpsUsed
|
||||
_gMilliAmpsUsed = 0;
|
||||
for (auto &bus : busses) {
|
||||
bus->show();
|
||||
_gMilliAmpsUsed += bus->getUsedCurrent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1356,85 +1003,6 @@ bool BusManager::canAllShow() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void BusManager::initializeABL() {
|
||||
_useABL = false; // reset
|
||||
if (_gMilliAmpsMax > 0) {
|
||||
// check global brightness limit
|
||||
for (auto &bus : busses) {
|
||||
if (bus->isDigital() && bus->getLEDCurrent() > 0) {
|
||||
_useABL = true; // at least one bus has valid LED current
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// check per bus brightness limit
|
||||
unsigned numABLbuses = 0;
|
||||
for (auto &bus : busses) {
|
||||
if (bus->isDigital() && bus->getLEDCurrent() > 0 && bus->getMaxCurrent() > 0)
|
||||
numABLbuses++; // count ABL enabled buses
|
||||
}
|
||||
if (numABLbuses > 0) {
|
||||
_useABL = true; // at least one bus has ABL set
|
||||
uint32_t ESPshare = MA_FOR_ESP / numABLbuses; // share of ESP current per ABL bus
|
||||
for (auto &bus : busses) {
|
||||
if (bus->isDigital()) {
|
||||
BusDigital &busd = static_cast<BusDigital&>(*bus);
|
||||
uint32_t busLength = busd.getLength();
|
||||
uint32_t busDemand = busLength * busd.getLEDCurrent();
|
||||
uint32_t busMax = busd.getMaxCurrent();
|
||||
if (busMax > ESPshare) busMax -= ESPshare;
|
||||
if (busMax < busLength) busMax = busLength; // give each LED 1mA, ABL will dim down to minimum
|
||||
if (busDemand == 0) busMax = 0; // no LED current set, disable ABL for this bus
|
||||
busd.setCurrentLimit(busMax);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BusManager::applyABL() {
|
||||
if (_useABL) {
|
||||
unsigned milliAmpsSum = 0; // use temporary variable to always return a valid _gMilliAmpsUsed to UI
|
||||
unsigned totalLEDs = 0;
|
||||
for (auto &bus : busses) {
|
||||
if (bus->isDigital() && bus->isOk()) {
|
||||
BusDigital &busd = static_cast<BusDigital&>(*bus);
|
||||
busd.estimateCurrent(); // sets _milliAmpsTotal, current is estimated for all buses even if they have the limit set to 0
|
||||
if (_gMilliAmpsMax == 0)
|
||||
busd.applyBriLimit(0); // apply per bus ABL limit, updates _milliAmpsTotal if limit reached
|
||||
milliAmpsSum += busd.getUsedCurrent();
|
||||
totalLEDs += busd.getLength(); // sum total number of LEDs for global Limit
|
||||
}
|
||||
}
|
||||
// check global current limit and apply global ABL limit, total current is summed above
|
||||
if (_gMilliAmpsMax > 0) {
|
||||
uint8_t newBri = 255;
|
||||
uint32_t globalMax = _gMilliAmpsMax > MA_FOR_ESP ? _gMilliAmpsMax - MA_FOR_ESP : 1; // subtract ESP current consumption, fully limit if too low
|
||||
if (globalMax > totalLEDs) { // check if budget is larger than standby current
|
||||
if (milliAmpsSum > globalMax) {
|
||||
newBri = globalMax * 255 / milliAmpsSum + 1; // scale brightness down to stay in current limit, +1 to avoid 0 brightness
|
||||
milliAmpsSum = globalMax; // update total used current
|
||||
}
|
||||
} else {
|
||||
newBri = 1; // limit too low, set brightness to minimum
|
||||
milliAmpsSum = totalLEDs; // estimate total used current as minimum
|
||||
}
|
||||
|
||||
// apply brightness limit to each bus, if its 255 it will only reset _colorSum
|
||||
for (auto &bus : busses) {
|
||||
if (bus->isDigital() && bus->isOk()) {
|
||||
BusDigital &busd = static_cast<BusDigital&>(*bus);
|
||||
if (busd.getLEDCurrent() > 0) // skip buses with LED current set to 0
|
||||
busd.applyBriLimit(newBri);
|
||||
}
|
||||
}
|
||||
}
|
||||
_gMilliAmpsUsed = milliAmpsSum;
|
||||
}
|
||||
else
|
||||
_gMilliAmpsUsed = 0; // reset, we have no current estimation without ABL
|
||||
}
|
||||
|
||||
ColorOrderMap& BusManager::getColorOrderMap() { return _colorOrderMap; }
|
||||
|
||||
|
||||
@@ -1450,4 +1018,3 @@ uint16_t BusDigital::_milliAmpsTotal = 0;
|
||||
std::vector<std::unique_ptr<Bus>> BusManager::busses;
|
||||
uint16_t BusManager::_gMilliAmpsUsed = 0;
|
||||
uint16_t BusManager::_gMilliAmpsMax = ABL_MILLIAMPS_DEFAULT;
|
||||
bool BusManager::_useABL = false;
|
||||
|
||||
@@ -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())
|
||||
@@ -247,6 +238,7 @@ class BusDigital : public Bus {
|
||||
|
||||
void show() override;
|
||||
bool canShow() const override;
|
||||
void setBrightness(uint8_t b) override;
|
||||
void setStatusPixel(uint32_t c) override;
|
||||
[[gnu::hot]] void setPixelColor(unsigned pix, uint32_t c) override;
|
||||
void setColorOrder(uint8_t colorOrder) override;
|
||||
@@ -258,9 +250,6 @@ class BusDigital : public Bus {
|
||||
uint16_t getLEDCurrent() const override { return _milliAmpsPerLed; }
|
||||
uint16_t getUsedCurrent() const override { return _milliAmpsTotal; }
|
||||
uint16_t getMaxCurrent() const override { return _milliAmpsMax; }
|
||||
void setCurrentLimit(uint16_t milliAmps) { _milliAmpsLimit = milliAmps; }
|
||||
void estimateCurrent(); // estimate used current from summed colors
|
||||
void applyBriLimit(uint8_t newBri);
|
||||
size_t getBusSize() const override;
|
||||
void begin() override;
|
||||
void cleanup();
|
||||
@@ -273,10 +262,8 @@ class BusDigital : public Bus {
|
||||
uint8_t _pins[2];
|
||||
uint8_t _iType;
|
||||
uint16_t _frequencykHz;
|
||||
uint16_t _milliAmpsMax;
|
||||
uint8_t _milliAmpsPerLed;
|
||||
uint16_t _milliAmpsLimit;
|
||||
uint32_t _colorSum; // total color value for the bus, updated in setPixelColor(), used to estimate current
|
||||
uint16_t _milliAmpsMax;
|
||||
void *_busPtr;
|
||||
|
||||
static uint16_t _milliAmpsTotal; // is overwitten/recalculated on each show()
|
||||
@@ -291,6 +278,8 @@ class BusDigital : public Bus {
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
uint8_t estimateCurrentAndLimitBri() const;
|
||||
};
|
||||
|
||||
|
||||
@@ -372,37 +361,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 +372,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;
|
||||
@@ -464,8 +422,8 @@ struct BusConfig {
|
||||
};
|
||||
|
||||
|
||||
// milliamps used by ESP (for power estimation)
|
||||
// you can set it to 0 if the ESP is powered by USB and the LEDs by external
|
||||
//fine tune power estimation constants for your setup
|
||||
//you can set it to 0 if the ESP is powered by USB and the LEDs by external
|
||||
#ifndef MA_FOR_ESP
|
||||
#ifdef ESP8266
|
||||
#define MA_FOR_ESP 80 //how much mA does the ESP use (Wemos D1 about 80mA)
|
||||
@@ -480,7 +438,6 @@ namespace BusManager {
|
||||
//extern std::vector<Bus*> busses;
|
||||
extern uint16_t _gMilliAmpsUsed;
|
||||
extern uint16_t _gMilliAmpsMax;
|
||||
extern bool _useABL;
|
||||
|
||||
#ifdef ESP32_DATA_IDLE_HIGH
|
||||
void esp32RMTInvertIdle() ;
|
||||
@@ -496,8 +453,6 @@ namespace BusManager {
|
||||
//inline uint16_t ablMilliampsMax() { unsigned sum = 0; for (auto &bus : busses) sum += bus->getMaxCurrent(); return sum; }
|
||||
inline uint16_t ablMilliampsMax() { return _gMilliAmpsMax; } // used for compatibility reasons (and enabling virtual global ABL)
|
||||
inline void setMilliampsMax(uint16_t max) { _gMilliAmpsMax = max;}
|
||||
void initializeABL(); // setup automatic brightness limiter parameters, call once after buses are initialized
|
||||
void applyABL(); // apply automatic brightness limiter, global or per bus
|
||||
|
||||
void useParallelOutput(); // workaround for inaccessible PolyBus
|
||||
bool hasParallelOutput(); // workaround for inaccessible PolyBus
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#define BusWrapper_h
|
||||
|
||||
//#define NPB_CONF_4STEP_CADENCE
|
||||
#include "NeoPixelBus.h"
|
||||
#include "NeoPixelBusLg.h"
|
||||
|
||||
//Hardware SPI Pins
|
||||
#define P_8266_HS_MOSI 13
|
||||
@@ -141,65 +141,65 @@
|
||||
/*** ESP8266 Neopixel methods ***/
|
||||
#ifdef ESP8266
|
||||
//RGB
|
||||
#define B_8266_U0_NEO_3 NeoPixelBus<NeoGrbFeature, NeoEsp8266Uart0Ws2813Method> //3 chan, esp8266, gpio1
|
||||
#define B_8266_U1_NEO_3 NeoPixelBus<NeoGrbFeature, NeoEsp8266Uart1Ws2813Method> //3 chan, esp8266, gpio2
|
||||
#define B_8266_DM_NEO_3 NeoPixelBus<NeoGrbFeature, NeoEsp8266Dma800KbpsMethod> //3 chan, esp8266, gpio3
|
||||
#define B_8266_BB_NEO_3 NeoPixelBus<NeoGrbFeature, NeoEsp8266BitBang800KbpsMethod> //3 chan, esp8266, bb (any pin but 16)
|
||||
#define B_8266_U0_NEO_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp8266Uart0Ws2813Method, NeoGammaNullMethod> //3 chan, esp8266, gpio1
|
||||
#define B_8266_U1_NEO_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp8266Uart1Ws2813Method, NeoGammaNullMethod> //3 chan, esp8266, gpio2
|
||||
#define B_8266_DM_NEO_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp8266Dma800KbpsMethod, NeoGammaNullMethod> //3 chan, esp8266, gpio3
|
||||
#define B_8266_BB_NEO_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp8266BitBang800KbpsMethod, NeoGammaNullMethod> //3 chan, esp8266, bb (any pin but 16)
|
||||
//RGBW
|
||||
#define B_8266_U0_NEO_4 NeoPixelBus<NeoGrbwFeature, NeoEsp8266Uart0Ws2813Method> //4 chan, esp8266, gpio1
|
||||
#define B_8266_U1_NEO_4 NeoPixelBus<NeoGrbwFeature, NeoEsp8266Uart1Ws2813Method> //4 chan, esp8266, gpio2
|
||||
#define B_8266_DM_NEO_4 NeoPixelBus<NeoGrbwFeature, NeoEsp8266Dma800KbpsMethod> //4 chan, esp8266, gpio3
|
||||
#define B_8266_BB_NEO_4 NeoPixelBus<NeoGrbwFeature, NeoEsp8266BitBang800KbpsMethod> //4 chan, esp8266, bb (any pin)
|
||||
#define B_8266_U0_NEO_4 NeoPixelBusLg<NeoGrbwFeature, NeoEsp8266Uart0Ws2813Method, NeoGammaNullMethod> //4 chan, esp8266, gpio1
|
||||
#define B_8266_U1_NEO_4 NeoPixelBusLg<NeoGrbwFeature, NeoEsp8266Uart1Ws2813Method, NeoGammaNullMethod> //4 chan, esp8266, gpio2
|
||||
#define B_8266_DM_NEO_4 NeoPixelBusLg<NeoGrbwFeature, NeoEsp8266Dma800KbpsMethod, NeoGammaNullMethod> //4 chan, esp8266, gpio3
|
||||
#define B_8266_BB_NEO_4 NeoPixelBusLg<NeoGrbwFeature, NeoEsp8266BitBang800KbpsMethod, NeoGammaNullMethod> //4 chan, esp8266, bb (any pin)
|
||||
//400Kbps
|
||||
#define B_8266_U0_400_3 NeoPixelBus<NeoGrbFeature, NeoEsp8266Uart0400KbpsMethod> //3 chan, esp8266, gpio1
|
||||
#define B_8266_U1_400_3 NeoPixelBus<NeoGrbFeature, NeoEsp8266Uart1400KbpsMethod> //3 chan, esp8266, gpio2
|
||||
#define B_8266_DM_400_3 NeoPixelBus<NeoGrbFeature, NeoEsp8266Dma400KbpsMethod> //3 chan, esp8266, gpio3
|
||||
#define B_8266_BB_400_3 NeoPixelBus<NeoGrbFeature, NeoEsp8266BitBang400KbpsMethod> //3 chan, esp8266, bb (any pin)
|
||||
#define B_8266_U0_400_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp8266Uart0400KbpsMethod, NeoGammaNullMethod> //3 chan, esp8266, gpio1
|
||||
#define B_8266_U1_400_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp8266Uart1400KbpsMethod, NeoGammaNullMethod> //3 chan, esp8266, gpio2
|
||||
#define B_8266_DM_400_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp8266Dma400KbpsMethod, NeoGammaNullMethod> //3 chan, esp8266, gpio3
|
||||
#define B_8266_BB_400_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp8266BitBang400KbpsMethod, NeoGammaNullMethod> //3 chan, esp8266, bb (any pin)
|
||||
//TM1814 (RGBW)
|
||||
#define B_8266_U0_TM1_4 NeoPixelBus<NeoWrgbTm1814Feature, NeoEsp8266Uart0Tm1814Method>
|
||||
#define B_8266_U1_TM1_4 NeoPixelBus<NeoWrgbTm1814Feature, NeoEsp8266Uart1Tm1814Method>
|
||||
#define B_8266_DM_TM1_4 NeoPixelBus<NeoWrgbTm1814Feature, NeoEsp8266DmaTm1814Method>
|
||||
#define B_8266_BB_TM1_4 NeoPixelBus<NeoWrgbTm1814Feature, NeoEsp8266BitBangTm1814Method>
|
||||
#define B_8266_U0_TM1_4 NeoPixelBusLg<NeoWrgbTm1814Feature, NeoEsp8266Uart0Tm1814Method, NeoGammaNullMethod>
|
||||
#define B_8266_U1_TM1_4 NeoPixelBusLg<NeoWrgbTm1814Feature, NeoEsp8266Uart1Tm1814Method, NeoGammaNullMethod>
|
||||
#define B_8266_DM_TM1_4 NeoPixelBusLg<NeoWrgbTm1814Feature, NeoEsp8266DmaTm1814Method, NeoGammaNullMethod>
|
||||
#define B_8266_BB_TM1_4 NeoPixelBusLg<NeoWrgbTm1814Feature, NeoEsp8266BitBangTm1814Method, NeoGammaNullMethod>
|
||||
//TM1829 (RGB)
|
||||
#define B_8266_U0_TM2_3 NeoPixelBus<NeoBrgFeature, NeoEsp8266Uart0Tm1829Method>
|
||||
#define B_8266_U1_TM2_3 NeoPixelBus<NeoBrgFeature, NeoEsp8266Uart1Tm1829Method>
|
||||
#define B_8266_DM_TM2_3 NeoPixelBus<NeoBrgFeature, NeoEsp8266DmaTm1829Method>
|
||||
#define B_8266_BB_TM2_3 NeoPixelBus<NeoBrgFeature, NeoEsp8266BitBangTm1829Method>
|
||||
#define B_8266_U0_TM2_3 NeoPixelBusLg<NeoBrgFeature, NeoEsp8266Uart0Tm1829Method, NeoGammaNullMethod>
|
||||
#define B_8266_U1_TM2_3 NeoPixelBusLg<NeoBrgFeature, NeoEsp8266Uart1Tm1829Method, NeoGammaNullMethod>
|
||||
#define B_8266_DM_TM2_3 NeoPixelBusLg<NeoBrgFeature, NeoEsp8266DmaTm1829Method, NeoGammaNullMethod>
|
||||
#define B_8266_BB_TM2_3 NeoPixelBusLg<NeoBrgFeature, NeoEsp8266BitBangTm1829Method, NeoGammaNullMethod>
|
||||
//UCS8903
|
||||
#define B_8266_U0_UCS_3 NeoPixelBus<NeoRgbUcs8903Feature, NeoEsp8266Uart0Ws2813Method> //3 chan, esp8266, gpio1
|
||||
#define B_8266_U1_UCS_3 NeoPixelBus<NeoRgbUcs8903Feature, NeoEsp8266Uart1Ws2813Method> //3 chan, esp8266, gpio2
|
||||
#define B_8266_DM_UCS_3 NeoPixelBus<NeoRgbUcs8903Feature, NeoEsp8266Dma800KbpsMethod> //3 chan, esp8266, gpio3
|
||||
#define B_8266_BB_UCS_3 NeoPixelBus<NeoRgbUcs8903Feature, NeoEsp8266BitBang800KbpsMethod> //3 chan, esp8266, bb (any pin but 16)
|
||||
#define B_8266_U0_UCS_3 NeoPixelBusLg<NeoRgbUcs8903Feature, NeoEsp8266Uart0Ws2813Method, NeoGammaNullMethod> //3 chan, esp8266, gpio1
|
||||
#define B_8266_U1_UCS_3 NeoPixelBusLg<NeoRgbUcs8903Feature, NeoEsp8266Uart1Ws2813Method, NeoGammaNullMethod> //3 chan, esp8266, gpio2
|
||||
#define B_8266_DM_UCS_3 NeoPixelBusLg<NeoRgbUcs8903Feature, NeoEsp8266Dma800KbpsMethod, NeoGammaNullMethod> //3 chan, esp8266, gpio3
|
||||
#define B_8266_BB_UCS_3 NeoPixelBusLg<NeoRgbUcs8903Feature, NeoEsp8266BitBang800KbpsMethod, NeoGammaNullMethod> //3 chan, esp8266, bb (any pin but 16)
|
||||
//UCS8904 RGBW
|
||||
#define B_8266_U0_UCS_4 NeoPixelBus<NeoRgbwUcs8904Feature, NeoEsp8266Uart0Ws2813Method> //4 chan, esp8266, gpio1
|
||||
#define B_8266_U1_UCS_4 NeoPixelBus<NeoRgbwUcs8904Feature, NeoEsp8266Uart1Ws2813Method> //4 chan, esp8266, gpio2
|
||||
#define B_8266_DM_UCS_4 NeoPixelBus<NeoRgbwUcs8904Feature, NeoEsp8266Dma800KbpsMethod> //4 chan, esp8266, gpio3
|
||||
#define B_8266_BB_UCS_4 NeoPixelBus<NeoRgbwUcs8904Feature, NeoEsp8266BitBang800KbpsMethod> //4 chan, esp8266, bb (any pin)
|
||||
#define B_8266_U0_UCS_4 NeoPixelBusLg<NeoRgbwUcs8904Feature, NeoEsp8266Uart0Ws2813Method, NeoGammaNullMethod> //4 chan, esp8266, gpio1
|
||||
#define B_8266_U1_UCS_4 NeoPixelBusLg<NeoRgbwUcs8904Feature, NeoEsp8266Uart1Ws2813Method, NeoGammaNullMethod> //4 chan, esp8266, gpio2
|
||||
#define B_8266_DM_UCS_4 NeoPixelBusLg<NeoRgbwUcs8904Feature, NeoEsp8266Dma800KbpsMethod, NeoGammaNullMethod> //4 chan, esp8266, gpio3
|
||||
#define B_8266_BB_UCS_4 NeoPixelBusLg<NeoRgbwUcs8904Feature, NeoEsp8266BitBang800KbpsMethod, NeoGammaNullMethod> //4 chan, esp8266, bb (any pin)
|
||||
//APA106
|
||||
#define B_8266_U0_APA106_3 NeoPixelBus<NeoRbgFeature, NeoEsp8266Uart0Apa106Method> //3 chan, esp8266, gpio1
|
||||
#define B_8266_U1_APA106_3 NeoPixelBus<NeoRbgFeature, NeoEsp8266Uart1Apa106Method> //3 chan, esp8266, gpio2
|
||||
#define B_8266_DM_APA106_3 NeoPixelBus<NeoGrbFeature, NeoEsp8266DmaApa106Method> //3 chan, esp8266, gpio3
|
||||
#define B_8266_BB_APA106_3 NeoPixelBus<NeoGrbFeature, NeoEsp8266BitBangApa106Method> //3 chan, esp8266, bb (any pin but 16)
|
||||
#define B_8266_U0_APA106_3 NeoPixelBusLg<NeoRbgFeature, NeoEsp8266Uart0Apa106Method, NeoGammaNullMethod> //3 chan, esp8266, gpio1
|
||||
#define B_8266_U1_APA106_3 NeoPixelBusLg<NeoRbgFeature, NeoEsp8266Uart1Apa106Method, NeoGammaNullMethod> //3 chan, esp8266, gpio2
|
||||
#define B_8266_DM_APA106_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp8266DmaApa106Method, NeoGammaNullMethod> //3 chan, esp8266, gpio3
|
||||
#define B_8266_BB_APA106_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp8266BitBangApa106Method, NeoGammaNullMethod> //3 chan, esp8266, bb (any pin but 16)
|
||||
//FW1906 GRBCW
|
||||
#define B_8266_U0_FW6_5 NeoPixelBus<NeoGrbcwxFeature, NeoEsp8266Uart0Ws2813Method> //esp8266, gpio1
|
||||
#define B_8266_U1_FW6_5 NeoPixelBus<NeoGrbcwxFeature, NeoEsp8266Uart1Ws2813Method> //esp8266, gpio2
|
||||
#define B_8266_DM_FW6_5 NeoPixelBus<NeoGrbcwxFeature, NeoEsp8266Dma800KbpsMethod> //esp8266, gpio3
|
||||
#define B_8266_BB_FW6_5 NeoPixelBus<NeoGrbcwxFeature, NeoEsp8266BitBang800KbpsMethod> //esp8266, bb
|
||||
#define B_8266_U0_FW6_5 NeoPixelBusLg<NeoGrbcwxFeature, NeoEsp8266Uart0Ws2813Method, NeoGammaNullMethod> //esp8266, gpio1
|
||||
#define B_8266_U1_FW6_5 NeoPixelBusLg<NeoGrbcwxFeature, NeoEsp8266Uart1Ws2813Method, NeoGammaNullMethod> //esp8266, gpio2
|
||||
#define B_8266_DM_FW6_5 NeoPixelBusLg<NeoGrbcwxFeature, NeoEsp8266Dma800KbpsMethod, NeoGammaNullMethod> //esp8266, gpio3
|
||||
#define B_8266_BB_FW6_5 NeoPixelBusLg<NeoGrbcwxFeature, NeoEsp8266BitBang800KbpsMethod, NeoGammaNullMethod> //esp8266, bb
|
||||
//WS2805 GRBCW
|
||||
#define B_8266_U0_2805_5 NeoPixelBus<NeoGrbwwFeature, NeoEsp8266Uart0Ws2805Method> //esp8266, gpio1
|
||||
#define B_8266_U1_2805_5 NeoPixelBus<NeoGrbwwFeature, NeoEsp8266Uart1Ws2805Method> //esp8266, gpio2
|
||||
#define B_8266_DM_2805_5 NeoPixelBus<NeoGrbwwFeature, NeoEsp8266DmaWs2805Method> //esp8266, gpio3
|
||||
#define B_8266_BB_2805_5 NeoPixelBus<NeoGrbwwFeature, NeoEsp8266BitBangWs2805Method> //esp8266, bb
|
||||
#define B_8266_U0_2805_5 NeoPixelBusLg<NeoGrbwwFeature, NeoEsp8266Uart0Ws2805Method, NeoGammaNullMethod> //esp8266, gpio1
|
||||
#define B_8266_U1_2805_5 NeoPixelBusLg<NeoGrbwwFeature, NeoEsp8266Uart1Ws2805Method, NeoGammaNullMethod> //esp8266, gpio2
|
||||
#define B_8266_DM_2805_5 NeoPixelBusLg<NeoGrbwwFeature, NeoEsp8266DmaWs2805Method, NeoGammaNullMethod> //esp8266, gpio3
|
||||
#define B_8266_BB_2805_5 NeoPixelBusLg<NeoGrbwwFeature, NeoEsp8266BitBangWs2805Method, NeoGammaNullMethod> //esp8266, bb
|
||||
//TM1914 (RGB)
|
||||
#define B_8266_U0_TM1914_3 NeoPixelBus<NeoRgbTm1914Feature, NeoEsp8266Uart0Tm1914Method>
|
||||
#define B_8266_U1_TM1914_3 NeoPixelBus<NeoRgbTm1914Feature, NeoEsp8266Uart1Tm1914Method>
|
||||
#define B_8266_DM_TM1914_3 NeoPixelBus<NeoRgbTm1914Feature, NeoEsp8266DmaTm1914Method>
|
||||
#define B_8266_BB_TM1914_3 NeoPixelBus<NeoRgbTm1914Feature, NeoEsp8266BitBangTm1914Method>
|
||||
#define B_8266_U0_TM1914_3 NeoPixelBusLg<NeoRgbTm1914Feature, NeoEsp8266Uart0Tm1914Method, NeoGammaNullMethod>
|
||||
#define B_8266_U1_TM1914_3 NeoPixelBusLg<NeoRgbTm1914Feature, NeoEsp8266Uart1Tm1914Method, NeoGammaNullMethod>
|
||||
#define B_8266_DM_TM1914_3 NeoPixelBusLg<NeoRgbTm1914Feature, NeoEsp8266DmaTm1914Method, NeoGammaNullMethod>
|
||||
#define B_8266_BB_TM1914_3 NeoPixelBusLg<NeoRgbTm1914Feature, NeoEsp8266BitBangTm1914Method, NeoGammaNullMethod>
|
||||
//Sm16825 (RGBWC)
|
||||
#define B_8266_U0_SM16825_5 NeoPixelBus<NeoRgbwcSm16825eFeature, NeoEsp8266Uart0Ws2813Method>
|
||||
#define B_8266_U1_SM16825_5 NeoPixelBus<NeoRgbwcSm16825eFeature, NeoEsp8266Uart1Ws2813Method>
|
||||
#define B_8266_DM_SM16825_5 NeoPixelBus<NeoRgbwcSm16825eFeature, NeoEsp8266Dma800KbpsMethod>
|
||||
#define B_8266_BB_SM16825_5 NeoPixelBus<NeoRgbwcSm16825eFeature, NeoEsp8266BitBangWs2813Method>
|
||||
#define B_8266_U0_SM16825_5 NeoPixelBusLg<NeoRgbwcSm16825eFeature, NeoEsp8266Uart0Ws2813Method, NeoGammaNullMethod>
|
||||
#define B_8266_U1_SM16825_5 NeoPixelBusLg<NeoRgbwcSm16825eFeature, NeoEsp8266Uart1Ws2813Method, NeoGammaNullMethod>
|
||||
#define B_8266_DM_SM16825_5 NeoPixelBusLg<NeoRgbwcSm16825eFeature, NeoEsp8266Dma800KbpsMethod, NeoGammaNullMethod>
|
||||
#define B_8266_BB_SM16825_5 NeoPixelBusLg<NeoRgbwcSm16825eFeature, NeoEsp8266BitBangWs2813Method, NeoGammaNullMethod>
|
||||
#endif
|
||||
|
||||
/*** ESP32 Neopixel methods ***/
|
||||
@@ -244,93 +244,85 @@
|
||||
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_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)
|
||||
#define B_32_RN_NEO_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp32RmtNWs2812xMethod, NeoGammaNullMethod> // ESP32, S2, S3, C3
|
||||
//#define B_32_IN_NEO_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp32I2sNWs2812xMethod, NeoGammaNullMethod> // ESP32 (dynamic I2S selection)
|
||||
#define B_32_I2_NEO_3 NeoPixelBusLg<NeoGrbFeature, X1Ws2812xMethod, NeoGammaNullMethod> // ESP32, S2, S3 (automatic I2S selection, see typedef above)
|
||||
#define B_32_IP_NEO_3 NeoPixelBusLg<NeoGrbFeature, X8Ws2812xMethod, NeoGammaNullMethod> // parallel I2S (ESP32, S2, S3)
|
||||
//RGBW
|
||||
#define B_32_RN_NEO_4 NeoPixelBus<NeoGrbwFeature, NeoEsp32RmtMethod(Sk6812)>
|
||||
#define B_32_I2_NEO_4 NeoPixelBus<NeoGrbwFeature, X1Sk6812Method>
|
||||
#define B_32_IP_NEO_4 NeoPixelBus<NeoGrbwFeature, X8Sk6812Method> // parallel I2S
|
||||
#define B_32_RN_NEO_4 NeoPixelBusLg<NeoGrbwFeature, NeoEsp32RmtNSk6812Method, NeoGammaNullMethod>
|
||||
#define B_32_I2_NEO_4 NeoPixelBusLg<NeoGrbwFeature, X1Sk6812Method, NeoGammaNullMethod>
|
||||
#define B_32_IP_NEO_4 NeoPixelBusLg<NeoGrbwFeature, X8Sk6812Method, NeoGammaNullMethod> // parallel I2S
|
||||
//400Kbps
|
||||
#define B_32_RN_400_3 NeoPixelBus<NeoGrbFeature, NeoEsp32RmtMethod(400Kbps)>
|
||||
#define B_32_I2_400_3 NeoPixelBus<NeoGrbFeature, X1400KbpsMethod>
|
||||
#define B_32_IP_400_3 NeoPixelBus<NeoGrbFeature, X8400KbpsMethod> // parallel I2S
|
||||
#define B_32_RN_400_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp32RmtN400KbpsMethod, NeoGammaNullMethod>
|
||||
#define B_32_I2_400_3 NeoPixelBusLg<NeoGrbFeature, X1400KbpsMethod, NeoGammaNullMethod>
|
||||
#define B_32_IP_400_3 NeoPixelBusLg<NeoGrbFeature, X8400KbpsMethod, NeoGammaNullMethod> // parallel I2S
|
||||
//TM1814 (RGBW)
|
||||
#define B_32_RN_TM1_4 NeoPixelBus<NeoWrgbTm1814Feature, NeoEsp32RmtMethod(Tm1814)>
|
||||
#define B_32_I2_TM1_4 NeoPixelBus<NeoWrgbTm1814Feature, X1Tm1814Method>
|
||||
#define B_32_IP_TM1_4 NeoPixelBus<NeoWrgbTm1814Feature, X8Tm1814Method> // parallel I2S
|
||||
#define B_32_RN_TM1_4 NeoPixelBusLg<NeoWrgbTm1814Feature, NeoEsp32RmtNTm1814Method, NeoGammaNullMethod>
|
||||
#define B_32_I2_TM1_4 NeoPixelBusLg<NeoWrgbTm1814Feature, X1Tm1814Method, NeoGammaNullMethod>
|
||||
#define B_32_IP_TM1_4 NeoPixelBusLg<NeoWrgbTm1814Feature, X8Tm1814Method, NeoGammaNullMethod> // parallel I2S
|
||||
//TM1829 (RGB)
|
||||
#define B_32_RN_TM2_3 NeoPixelBus<NeoBrgFeature, NeoEsp32RmtMethod(Tm1829)>
|
||||
#define B_32_I2_TM2_3 NeoPixelBus<NeoBrgFeature, X1Tm1829Method>
|
||||
#define B_32_IP_TM2_3 NeoPixelBus<NeoBrgFeature, X8Tm1829Method> // parallel I2S
|
||||
#define B_32_RN_TM2_3 NeoPixelBusLg<NeoBrgFeature, NeoEsp32RmtNTm1829Method, NeoGammaNullMethod>
|
||||
#define B_32_I2_TM2_3 NeoPixelBusLg<NeoBrgFeature, X1Tm1829Method, NeoGammaNullMethod>
|
||||
#define B_32_IP_TM2_3 NeoPixelBusLg<NeoBrgFeature, X8Tm1829Method, NeoGammaNullMethod> // parallel I2S
|
||||
//UCS8903
|
||||
#define B_32_RN_UCS_3 NeoPixelBus<NeoRgbUcs8903Feature, NeoEsp32RmtMethod(Ws2812x)>
|
||||
#define B_32_I2_UCS_3 NeoPixelBus<NeoRgbUcs8903Feature, X1800KbpsMethod>
|
||||
#define B_32_IP_UCS_3 NeoPixelBus<NeoRgbUcs8903Feature, X8800KbpsMethod> // parallel I2S
|
||||
#define B_32_RN_UCS_3 NeoPixelBusLg<NeoRgbUcs8903Feature, NeoEsp32RmtNWs2812xMethod, NeoGammaNullMethod>
|
||||
#define B_32_I2_UCS_3 NeoPixelBusLg<NeoRgbUcs8903Feature, X1800KbpsMethod, NeoGammaNullMethod>
|
||||
#define B_32_IP_UCS_3 NeoPixelBusLg<NeoRgbUcs8903Feature, X8800KbpsMethod, NeoGammaNullMethod> // parallel I2S
|
||||
//UCS8904
|
||||
#define B_32_RN_UCS_4 NeoPixelBus<NeoRgbwUcs8904Feature, NeoEsp32RmtMethod(Ws2812x)>
|
||||
#define B_32_I2_UCS_4 NeoPixelBus<NeoRgbwUcs8904Feature, X1800KbpsMethod>
|
||||
#define B_32_IP_UCS_4 NeoPixelBus<NeoRgbwUcs8904Feature, X8800KbpsMethod>// parallel I2S
|
||||
#define B_32_RN_UCS_4 NeoPixelBusLg<NeoRgbwUcs8904Feature, NeoEsp32RmtNWs2812xMethod, NeoGammaNullMethod>
|
||||
#define B_32_I2_UCS_4 NeoPixelBusLg<NeoRgbwUcs8904Feature, X1800KbpsMethod, NeoGammaNullMethod>
|
||||
#define B_32_IP_UCS_4 NeoPixelBusLg<NeoRgbwUcs8904Feature, X8800KbpsMethod, NeoGammaNullMethod>// parallel I2S
|
||||
//APA106
|
||||
#define B_32_RN_APA106_3 NeoPixelBus<NeoGrbFeature, NeoEsp32RmtMethod(Apa106)>
|
||||
#define B_32_I2_APA106_3 NeoPixelBus<NeoGrbFeature, X1Apa106Method>
|
||||
#define B_32_IP_APA106_3 NeoPixelBus<NeoGrbFeature, X8Apa106Method> // parallel I2S
|
||||
#define B_32_RN_APA106_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp32RmtNApa106Method, NeoGammaNullMethod>
|
||||
#define B_32_I2_APA106_3 NeoPixelBusLg<NeoGrbFeature, X1Apa106Method, NeoGammaNullMethod>
|
||||
#define B_32_IP_APA106_3 NeoPixelBusLg<NeoGrbFeature, X8Apa106Method, NeoGammaNullMethod> // parallel I2S
|
||||
//FW1906 GRBCW
|
||||
#define B_32_RN_FW6_5 NeoPixelBus<NeoGrbcwxFeature, NeoEsp32RmtMethod(Ws2812x)>
|
||||
#define B_32_I2_FW6_5 NeoPixelBus<NeoGrbcwxFeature, X1800KbpsMethod>
|
||||
#define B_32_IP_FW6_5 NeoPixelBus<NeoGrbcwxFeature, X8800KbpsMethod> // parallel I2S
|
||||
#define B_32_RN_FW6_5 NeoPixelBusLg<NeoGrbcwxFeature, NeoEsp32RmtNWs2812xMethod, NeoGammaNullMethod>
|
||||
#define B_32_I2_FW6_5 NeoPixelBusLg<NeoGrbcwxFeature, X1800KbpsMethod, NeoGammaNullMethod>
|
||||
#define B_32_IP_FW6_5 NeoPixelBusLg<NeoGrbcwxFeature, X8800KbpsMethod, NeoGammaNullMethod> // parallel I2S
|
||||
//WS2805 RGBWC
|
||||
#define B_32_RN_2805_5 NeoPixelBus<NeoGrbwwFeature, NeoEsp32RmtMethod(Ws2805)>
|
||||
#define B_32_I2_2805_5 NeoPixelBus<NeoGrbwwFeature, X1Ws2805Method>
|
||||
#define B_32_IP_2805_5 NeoPixelBus<NeoGrbwwFeature, X8Ws2805Method> // parallel I2S
|
||||
#define B_32_RN_2805_5 NeoPixelBusLg<NeoGrbwwFeature, NeoEsp32RmtNWs2805Method, NeoGammaNullMethod>
|
||||
#define B_32_I2_2805_5 NeoPixelBusLg<NeoGrbwwFeature, X1Ws2805Method, NeoGammaNullMethod>
|
||||
#define B_32_IP_2805_5 NeoPixelBusLg<NeoGrbwwFeature, X8Ws2805Method, NeoGammaNullMethod> // parallel I2S
|
||||
//TM1914 (RGB)
|
||||
#define B_32_RN_TM1914_3 NeoPixelBus<NeoGrbTm1914Feature, NeoEsp32RmtMethod(Tm1914)>
|
||||
#define B_32_I2_TM1914_3 NeoPixelBus<NeoGrbTm1914Feature, X1Tm1914Method>
|
||||
#define B_32_IP_TM1914_3 NeoPixelBus<NeoGrbTm1914Feature, X8Tm1914Method> // parallel I2S
|
||||
#define B_32_RN_TM1914_3 NeoPixelBusLg<NeoGrbTm1914Feature, NeoEsp32RmtNTm1914Method, NeoGammaNullMethod>
|
||||
#define B_32_I2_TM1914_3 NeoPixelBusLg<NeoGrbTm1914Feature, X1Tm1914Method, NeoGammaNullMethod>
|
||||
#define B_32_IP_TM1914_3 NeoPixelBusLg<NeoGrbTm1914Feature, X8Tm1914Method, NeoGammaNullMethod> // parallel I2S
|
||||
//Sm16825 (RGBWC)
|
||||
#define B_32_RN_SM16825_5 NeoPixelBus<NeoRgbcwSm16825eFeature, NeoEsp32RmtMethod(Ws2812x)>
|
||||
#define B_32_I2_SM16825_5 NeoPixelBus<NeoRgbcwSm16825eFeature, X1Ws2812xMethod>
|
||||
#define B_32_IP_SM16825_5 NeoPixelBus<NeoRgbcwSm16825eFeature, X8Ws2812xMethod> // parallel I2S
|
||||
#define B_32_RN_SM16825_5 NeoPixelBusLg<NeoRgbcwSm16825eFeature, NeoEsp32RmtNWs2812xMethod, NeoGammaNullMethod>
|
||||
#define B_32_I2_SM16825_5 NeoPixelBusLg<NeoRgbcwSm16825eFeature, X1Ws2812xMethod, NeoGammaNullMethod>
|
||||
#define B_32_IP_SM16825_5 NeoPixelBusLg<NeoRgbcwSm16825eFeature, X8Ws2812xMethod, NeoGammaNullMethod> // parallel I2S
|
||||
#endif
|
||||
|
||||
//APA102
|
||||
#ifdef WLED_USE_ETHERNET
|
||||
// fix for #2542 (by @BlackBird77)
|
||||
#define B_HS_DOT_3 NeoPixelBus<DotStarBgrFeature, DotStarEsp32HspiHzMethod> //hardware HSPI (was DotStarEsp32DmaHspi5MhzMethod in NPB @ 2.6.9)
|
||||
#define B_HS_DOT_3 NeoPixelBusLg<DotStarBgrFeature, DotStarEsp32HspiHzMethod, NeoGammaNullMethod> //hardware HSPI (was DotStarEsp32DmaHspi5MhzMethod in NPB @ 2.6.9)
|
||||
#else
|
||||
#define B_HS_DOT_3 NeoPixelBus<DotStarBgrFeature, DotStarSpiHzMethod> //hardware VSPI
|
||||
#define B_HS_DOT_3 NeoPixelBusLg<DotStarBgrFeature, DotStarSpiHzMethod, NeoGammaNullMethod> //hardware VSPI
|
||||
#endif
|
||||
#define B_SS_DOT_3 NeoPixelBus<DotStarBgrFeature, DotStarMethod> //soft SPI
|
||||
#define B_SS_DOT_3 NeoPixelBusLg<DotStarBgrFeature, DotStarMethod, NeoGammaNullMethod> //soft SPI
|
||||
|
||||
//LPD8806
|
||||
#define B_HS_LPD_3 NeoPixelBus<Lpd8806GrbFeature, Lpd8806SpiHzMethod>
|
||||
#define B_SS_LPD_3 NeoPixelBus<Lpd8806GrbFeature, Lpd8806Method>
|
||||
#define B_HS_LPD_3 NeoPixelBusLg<Lpd8806GrbFeature, Lpd8806SpiHzMethod, NeoGammaNullMethod>
|
||||
#define B_SS_LPD_3 NeoPixelBusLg<Lpd8806GrbFeature, Lpd8806Method, NeoGammaNullMethod>
|
||||
|
||||
//LPD6803
|
||||
#define B_HS_LPO_3 NeoPixelBus<Lpd6803GrbFeature, Lpd6803SpiHzMethod>
|
||||
#define B_SS_LPO_3 NeoPixelBus<Lpd6803GrbFeature, Lpd6803Method>
|
||||
#define B_HS_LPO_3 NeoPixelBusLg<Lpd6803GrbFeature, Lpd6803SpiHzMethod, NeoGammaNullMethod>
|
||||
#define B_SS_LPO_3 NeoPixelBusLg<Lpd6803GrbFeature, Lpd6803Method, NeoGammaNullMethod>
|
||||
|
||||
//WS2801
|
||||
#ifdef WLED_USE_ETHERNET
|
||||
#define B_HS_WS1_3 NeoPixelBus<NeoRbgFeature, Ws2801MethodBase<TwoWireHspiImple<SpiSpeedHz>>>
|
||||
#define B_HS_WS1_3 NeoPixelBusLg<NeoRbgFeature, Ws2801MethodBase<TwoWireHspiImple<SpiSpeedHz>>, NeoGammaNullMethod>
|
||||
#else
|
||||
#define B_HS_WS1_3 NeoPixelBus<NeoRbgFeature, Ws2801SpiHzMethod>
|
||||
#define B_HS_WS1_3 NeoPixelBusLg<NeoRbgFeature, Ws2801SpiHzMethod, NeoGammaNullMethod>
|
||||
#endif
|
||||
#define B_SS_WS1_3 NeoPixelBus<NeoRbgFeature, Ws2801Method>
|
||||
#define B_SS_WS1_3 NeoPixelBusLg<NeoRbgFeature, Ws2801Method, NeoGammaNullMethod>
|
||||
|
||||
//P9813
|
||||
#define B_HS_P98_3 NeoPixelBus<P9813BgrFeature, P9813SpiHzMethod>
|
||||
#define B_SS_P98_3 NeoPixelBus<P9813BgrFeature, P9813Method>
|
||||
#define B_HS_P98_3 NeoPixelBusLg<P9813BgrFeature, P9813SpiHzMethod, NeoGammaNullMethod>
|
||||
#define B_SS_P98_3 NeoPixelBusLg<P9813BgrFeature, P9813Method, NeoGammaNullMethod>
|
||||
|
||||
// 48bit & 64bit to 24bit & 32bit RGB(W) conversion
|
||||
#define toRGBW32(c) (RGBW32((c>>40)&0xFF, (c>>24)&0xFF, (c>>8)&0xFF, (c>>56)&0xFF))
|
||||
@@ -904,6 +896,102 @@ class PolyBus {
|
||||
}
|
||||
}
|
||||
|
||||
static void setBrightness(void* busPtr, uint8_t busType, uint8_t b) {
|
||||
switch (busType) {
|
||||
case I_NONE: break;
|
||||
#ifdef ESP8266
|
||||
case I_8266_U0_NEO_3: (static_cast<B_8266_U0_NEO_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_U1_NEO_3: (static_cast<B_8266_U1_NEO_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_DM_NEO_3: (static_cast<B_8266_DM_NEO_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_BB_NEO_3: (static_cast<B_8266_BB_NEO_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_U0_NEO_4: (static_cast<B_8266_U0_NEO_4*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_U1_NEO_4: (static_cast<B_8266_U1_NEO_4*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_DM_NEO_4: (static_cast<B_8266_DM_NEO_4*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_BB_NEO_4: (static_cast<B_8266_BB_NEO_4*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_U0_400_3: (static_cast<B_8266_U0_400_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_U1_400_3: (static_cast<B_8266_U1_400_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_DM_400_3: (static_cast<B_8266_DM_400_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_BB_400_3: (static_cast<B_8266_BB_400_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_U0_TM1_4: (static_cast<B_8266_U0_TM1_4*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_U1_TM1_4: (static_cast<B_8266_U1_TM1_4*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_DM_TM1_4: (static_cast<B_8266_DM_TM1_4*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_BB_TM1_4: (static_cast<B_8266_BB_TM1_4*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_U0_TM2_3: (static_cast<B_8266_U0_TM2_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_U1_TM2_3: (static_cast<B_8266_U1_TM2_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_DM_TM2_3: (static_cast<B_8266_DM_TM2_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_BB_TM2_3: (static_cast<B_8266_BB_TM2_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_U0_UCS_3: (static_cast<B_8266_U0_UCS_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_U1_UCS_3: (static_cast<B_8266_U1_UCS_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_DM_UCS_3: (static_cast<B_8266_DM_UCS_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_BB_UCS_3: (static_cast<B_8266_BB_UCS_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_U0_UCS_4: (static_cast<B_8266_U0_UCS_4*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_U1_UCS_4: (static_cast<B_8266_U1_UCS_4*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_DM_UCS_4: (static_cast<B_8266_DM_UCS_4*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_BB_UCS_4: (static_cast<B_8266_BB_UCS_4*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_U0_APA106_3: (static_cast<B_8266_U0_APA106_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_U1_APA106_3: (static_cast<B_8266_U1_APA106_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_DM_APA106_3: (static_cast<B_8266_DM_APA106_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_BB_APA106_3: (static_cast<B_8266_BB_APA106_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_U0_FW6_5: (static_cast<B_8266_U0_FW6_5*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_U1_FW6_5: (static_cast<B_8266_U1_FW6_5*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_DM_FW6_5: (static_cast<B_8266_DM_FW6_5*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_BB_FW6_5: (static_cast<B_8266_BB_FW6_5*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_U0_2805_5: (static_cast<B_8266_U0_2805_5*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_U1_2805_5: (static_cast<B_8266_U1_2805_5*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_DM_2805_5: (static_cast<B_8266_DM_2805_5*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_BB_2805_5: (static_cast<B_8266_BB_2805_5*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_U0_TM1914_3: (static_cast<B_8266_U0_TM1914_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_U1_TM1914_3: (static_cast<B_8266_U1_TM1914_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_DM_TM1914_3: (static_cast<B_8266_DM_TM1914_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_BB_TM1914_3: (static_cast<B_8266_BB_TM1914_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_U0_SM16825_5: (static_cast<B_8266_U0_SM16825_5*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_U1_SM16825_5: (static_cast<B_8266_U1_SM16825_5*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_DM_SM16825_5: (static_cast<B_8266_DM_SM16825_5*>(busPtr))->SetLuminance(b); break;
|
||||
case I_8266_BB_SM16825_5: (static_cast<B_8266_BB_SM16825_5*>(busPtr))->SetLuminance(b); break;
|
||||
#endif
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
// RMT buses
|
||||
case I_32_RN_NEO_3: (static_cast<B_32_RN_NEO_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_32_RN_NEO_4: (static_cast<B_32_RN_NEO_4*>(busPtr))->SetLuminance(b); break;
|
||||
case I_32_RN_400_3: (static_cast<B_32_RN_400_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_32_RN_TM1_4: (static_cast<B_32_RN_TM1_4*>(busPtr))->SetLuminance(b); break;
|
||||
case I_32_RN_TM2_3: (static_cast<B_32_RN_TM2_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_32_RN_UCS_3: (static_cast<B_32_RN_UCS_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_32_RN_UCS_4: (static_cast<B_32_RN_UCS_4*>(busPtr))->SetLuminance(b); break;
|
||||
case I_32_RN_APA106_3: (static_cast<B_32_RN_APA106_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_32_RN_FW6_5: (static_cast<B_32_RN_FW6_5*>(busPtr))->SetLuminance(b); break;
|
||||
case I_32_RN_2805_5: (static_cast<B_32_RN_2805_5*>(busPtr))->SetLuminance(b); break;
|
||||
case I_32_RN_TM1914_3: (static_cast<B_32_RN_TM1914_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_32_RN_SM16825_5: (static_cast<B_32_RN_SM16825_5*>(busPtr))->SetLuminance(b); break;
|
||||
// I2S1 bus or paralell buses
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32C3
|
||||
case I_32_I2_NEO_3: if (_useParallelI2S) (static_cast<B_32_IP_NEO_3*>(busPtr))->SetLuminance(b); else (static_cast<B_32_I2_NEO_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_32_I2_NEO_4: if (_useParallelI2S) (static_cast<B_32_IP_NEO_4*>(busPtr))->SetLuminance(b); else (static_cast<B_32_I2_NEO_4*>(busPtr))->SetLuminance(b); break;
|
||||
case I_32_I2_400_3: if (_useParallelI2S) (static_cast<B_32_IP_400_3*>(busPtr))->SetLuminance(b); else (static_cast<B_32_I2_400_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_32_I2_TM1_4: if (_useParallelI2S) (static_cast<B_32_IP_TM1_4*>(busPtr))->SetLuminance(b); else (static_cast<B_32_I2_TM1_4*>(busPtr))->SetLuminance(b); break;
|
||||
case I_32_I2_TM2_3: if (_useParallelI2S) (static_cast<B_32_IP_TM2_3*>(busPtr))->SetLuminance(b); else (static_cast<B_32_I2_TM2_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_32_I2_UCS_3: if (_useParallelI2S) (static_cast<B_32_IP_UCS_3*>(busPtr))->SetLuminance(b); else (static_cast<B_32_I2_UCS_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_32_I2_UCS_4: if (_useParallelI2S) (static_cast<B_32_IP_UCS_4*>(busPtr))->SetLuminance(b); else (static_cast<B_32_I2_UCS_4*>(busPtr))->SetLuminance(b); break;
|
||||
case I_32_I2_APA106_3: if (_useParallelI2S) (static_cast<B_32_IP_APA106_3*>(busPtr))->SetLuminance(b); else (static_cast<B_32_I2_APA106_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_32_I2_FW6_5: if (_useParallelI2S) (static_cast<B_32_IP_FW6_5*>(busPtr))->SetLuminance(b); else (static_cast<B_32_I2_FW6_5*>(busPtr))->SetLuminance(b); break;
|
||||
case I_32_I2_2805_5: if (_useParallelI2S) (static_cast<B_32_IP_2805_5*>(busPtr))->SetLuminance(b); else (static_cast<B_32_I2_2805_5*>(busPtr))->SetLuminance(b); break;
|
||||
case I_32_I2_TM1914_3: if (_useParallelI2S) (static_cast<B_32_IP_TM1914_3*>(busPtr))->SetLuminance(b); else (static_cast<B_32_I2_TM1914_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_32_I2_SM16825_5: if (_useParallelI2S) (static_cast<B_32_IP_SM16825_5*>(busPtr))->SetLuminance(b); else (static_cast<B_32_I2_SM16825_5*>(busPtr))->SetLuminance(b); break;
|
||||
#endif
|
||||
#endif
|
||||
case I_HS_DOT_3: (static_cast<B_HS_DOT_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_SS_DOT_3: (static_cast<B_SS_DOT_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_HS_LPD_3: (static_cast<B_HS_LPD_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_SS_LPD_3: (static_cast<B_SS_LPD_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_HS_LPO_3: (static_cast<B_HS_LPO_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_SS_LPO_3: (static_cast<B_SS_LPO_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_HS_WS1_3: (static_cast<B_HS_WS1_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_SS_WS1_3: (static_cast<B_SS_WS1_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_HS_P98_3: (static_cast<B_HS_P98_3*>(busPtr))->SetLuminance(b); break;
|
||||
case I_SS_P98_3: (static_cast<B_SS_P98_3*>(busPtr))->SetLuminance(b); break;
|
||||
}
|
||||
}
|
||||
|
||||
[[gnu::hot]] static uint32_t getPixelColor(void* busPtr, uint8_t busType, uint16_t pix, uint8_t co) {
|
||||
RgbwColor col(0,0,0,0);
|
||||
switch (busType) {
|
||||
@@ -1121,54 +1209,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 +1308,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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -510,6 +519,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
|
||||
CJSON(briMultiplier, light[F("scale-bri")]);
|
||||
CJSON(paletteBlend, light[F("pal-mode")]);
|
||||
CJSON(strip.autoSegments, light[F("aseg")]);
|
||||
CJSON(useRainbowWheel, light[F("rw")]);
|
||||
|
||||
CJSON(gammaCorrectVal, light["gc"]["val"]); // default 2.2
|
||||
float light_gc_bri = light["gc"]["bri"];
|
||||
@@ -763,36 +773,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
|
||||
return (doc["sv"] | true);
|
||||
}
|
||||
|
||||
|
||||
static const char s_cfg_json[] PROGMEM = "/cfg.json";
|
||||
|
||||
bool backupConfig() {
|
||||
return backupFile(s_cfg_json);
|
||||
}
|
||||
|
||||
bool restoreConfig() {
|
||||
return restoreFile(s_cfg_json);
|
||||
}
|
||||
|
||||
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() {
|
||||
if (WLED_FS.exists(s_cfg_json)) {
|
||||
DEBUG_PRINTLN(F("Reset config"));
|
||||
char backupname[32];
|
||||
snprintf_P(backupname, sizeof(backupname), PSTR("/rst.%s"), &s_cfg_json[1]);
|
||||
WLED_FS.rename(s_cfg_json, backupname);
|
||||
doReboot = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool deserializeConfigFromFS() {
|
||||
[[maybe_unused]] bool success = deserializeConfigSec();
|
||||
#ifdef WLED_ADD_EEPROM_SUPPORT
|
||||
@@ -818,7 +801,6 @@ bool deserializeConfigFromFS() {
|
||||
|
||||
void serializeConfigToFS() {
|
||||
serializeConfigSec();
|
||||
backupConfig(); // backup before writing new config
|
||||
|
||||
DEBUG_PRINTLN(F("Writing settings to /cfg.json..."));
|
||||
|
||||
@@ -1060,6 +1042,7 @@ void serializeConfig(JsonObject root) {
|
||||
light[F("scale-bri")] = briMultiplier;
|
||||
light[F("pal-mode")] = paletteBlend;
|
||||
light[F("aseg")] = strip.autoSegments;
|
||||
light[F("rw")] = useRainbowWheel;
|
||||
|
||||
JsonObject light_gc = light.createNestedObject("gc");
|
||||
light_gc["bri"] = (gammaCorrectBri) ? gammaCorrectVal : 1.0f; // keep compatibility
|
||||
|
||||
@@ -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 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,62 +25,65 @@ 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;
|
||||
}
|
||||
|
||||
/*
|
||||
* fades color toward black
|
||||
* if using "video" method the resulting color will never become black unless it is already black
|
||||
*/
|
||||
uint32_t IRAM_ATTR color_fade(uint32_t c1, uint8_t amount, bool video) {
|
||||
if (c1 == 0 || amount == 0) return 0; // black or no change
|
||||
if (amount == 255) return c1;
|
||||
uint32_t addRemains = 0;
|
||||
|
||||
if (!video) amount++; // add one for correct scaling using bitshifts
|
||||
else {
|
||||
// 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
|
||||
uint32_t color_fade(uint32_t c1, uint8_t amount, bool video)
|
||||
{
|
||||
if (amount == 255) return c1;
|
||||
if (c1 == BLACK || amount == 0) return BLACK;
|
||||
uint32_t scaledcolor; // color order is: W R G B from MSB to LSB
|
||||
uint32_t scale = amount; // 32bit for faster calculation
|
||||
uint32_t addRemains = 0;
|
||||
if (!video) scale++; // add one for correct scaling using bitshifts
|
||||
else { // video scaling: make sure colors do not dim to zero if they started non-zero
|
||||
addRemains = R(c1) ? 0x00010000 : 0;
|
||||
addRemains |= G(c1) ? 0x00000100 : 0;
|
||||
addRemains |= B(c1) ? 0x00000001 : 0;
|
||||
addRemains |= W(c1) ? 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
|
||||
uint32_t wg = (((c1 >> 8) & TWO_CHANNEL_MASK) * amount) & ~TWO_CHANNEL_MASK; // scale white and green
|
||||
return (rb | wg) + addRemains;
|
||||
uint32_t rb = (((c1 & TWO_CHANNEL_MASK) * scale) >> 8) & TWO_CHANNEL_MASK; // scale red and blue
|
||||
uint32_t wg = (((c1 >> 8) & TWO_CHANNEL_MASK) * scale) & ~TWO_CHANNEL_MASK; // scale white and green
|
||||
scaledcolor = (rb | wg) + addRemains;
|
||||
return scaledcolor;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -89,19 +92,20 @@ 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)
|
||||
uint32_t ColorFromPaletteWLED(const CRGBPalette16& pal, unsigned index, uint8_t brightness, TBlendType blendType) {
|
||||
uint32_t ColorFromPaletteWLED(const CRGBPalette16& pal, unsigned index, uint8_t brightness, TBlendType blendType)
|
||||
{
|
||||
if (blendType == LINEARBLEND_NOWRAP) {
|
||||
index = (index * 0xF0) >> 8; // Blend range is affected by lo4 blend of values, remap to avoid wrapping
|
||||
}
|
||||
@@ -116,16 +120,16 @@ uint32_t ColorFromPaletteWLED(const CRGBPalette16& pal, unsigned index, uint8_t
|
||||
else ++entry;
|
||||
unsigned f2 = (lo4 << 4);
|
||||
unsigned f1 = 256 - f2;
|
||||
red1 = (red1 * f1 + (unsigned)entry->r * f2) >> 8; // note: using color_blend() is slower
|
||||
red1 = (red1 * f1 + (unsigned)entry->r * f2) >> 8; // note: using color_blend() is 20% slower
|
||||
green1 = (green1 * f1 + (unsigned)entry->g * f2) >> 8;
|
||||
blue1 = (blue1 * f1 + (unsigned)entry->b * f2) >> 8;
|
||||
blue1 = (blue1 * f1 + (unsigned)entry->b * f2) >> 8;
|
||||
}
|
||||
if (brightness < 255) { // note: zero checking could be done to return black but that is hardly ever used so it is omitted
|
||||
// actually same as color_fade(), using color_fade() is slower
|
||||
// actually color_fade(c1, brightness)
|
||||
uint32_t scale = brightness + 1; // adjust for rounding (bitshift)
|
||||
red1 = (red1 * scale) >> 8;
|
||||
red1 = (red1 * scale) >> 8; // note: using color_fade() is 30% slower
|
||||
green1 = (green1 * scale) >> 8;
|
||||
blue1 = (blue1 * scale) >> 8;
|
||||
blue1 = (blue1 * scale) >> 8;
|
||||
}
|
||||
return RGBW32(red1,green1,blue1,0);
|
||||
}
|
||||
@@ -249,7 +253,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);
|
||||
|
||||
@@ -585,22 +589,34 @@ uint8_t NeoGammaWLEDMethod::gammaT_inv[256];
|
||||
void NeoGammaWLEDMethod::calcGammaTable(float gamma)
|
||||
{
|
||||
float gamma_inv = 1.0f / gamma; // inverse gamma
|
||||
for (size_t i = 1; i < 256; i++) {
|
||||
for (size_t i = 0; i < 256; i++) {
|
||||
gammaT[i] = (int)(powf((float)i / 255.0f, gamma) * 255.0f + 0.5f);
|
||||
gammaT_inv[i] = (int)(powf(((float)i - 0.5f) / 255.0f, gamma_inv) * 255.0f + 0.5f);
|
||||
//DEBUG_PRINTF_P(PSTR("gammaT[%d] = %d gammaT_inv[%d] = %d\n"), i, gammaT[i], i, gammaT_inv[i]);
|
||||
gammaT_inv[i] = (int)(powf((float)i / 255.0f, gamma_inv) * 255.0f + 0.5f);
|
||||
}
|
||||
gammaT[0] = 0;
|
||||
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)
|
||||
// used for color gamma correction
|
||||
uint32_t IRAM_ATTR_YN NeoGammaWLEDMethod::Correct32(uint32_t color)
|
||||
{
|
||||
if (!gammaCorrectCol) return color;
|
||||
uint8_t w = W(color);
|
||||
uint8_t r = R(color);
|
||||
uint8_t g = G(color);
|
||||
uint8_t b = B(color);
|
||||
w = gammaT[w];
|
||||
r = gammaT[r];
|
||||
g = gammaT[g];
|
||||
b = gammaT[b];
|
||||
return RGBW32(r, g, b, w);
|
||||
}
|
||||
|
||||
uint32_t IRAM_ATTR_YN NeoGammaWLEDMethod::inverseGamma32(uint32_t color)
|
||||
{
|
||||
if (!gammaCorrectCol) return color;
|
||||
uint8_t w = W(color);
|
||||
|
||||
155
wled00/colors.h
155
wled00/colors.h
@@ -1,155 +0,0 @@
|
||||
#pragma once
|
||||
#ifndef WLED_COLORS_H
|
||||
#define WLED_COLORS_H
|
||||
|
||||
/*
|
||||
* Color structs and color utility functions
|
||||
*/
|
||||
#include <vector>
|
||||
#include "FastLED.h"
|
||||
|
||||
#define ColorFromPalette ColorFromPaletteWLED // override fastled version
|
||||
|
||||
// CRGBW can be used to manipulate 32bit colors faster. However: if it is passed to functions, it adds overhead compared to a uint32_t color
|
||||
// use with caution and pay attention to flash size. Usually converting a uint32_t to CRGBW to extract r, g, b, w values is slower than using bitshifts
|
||||
// it can be useful to avoid back and forth conversions between uint32_t and fastled CRGB
|
||||
struct CRGBW {
|
||||
union {
|
||||
uint32_t color32; // Access as a 32-bit value (0xWWRRGGBB)
|
||||
struct {
|
||||
uint8_t b;
|
||||
uint8_t g;
|
||||
uint8_t r;
|
||||
uint8_t w;
|
||||
};
|
||||
uint8_t raw[4]; // Access as an array in the order B, G, R, W
|
||||
};
|
||||
|
||||
// Default constructor
|
||||
inline CRGBW() __attribute__((always_inline)) = default;
|
||||
|
||||
// Constructor from a 32-bit color (0xWWRRGGBB)
|
||||
constexpr CRGBW(uint32_t color) __attribute__((always_inline)) : color32(color) {}
|
||||
|
||||
// Constructor with r, g, b, w values
|
||||
constexpr CRGBW(uint8_t red, uint8_t green, uint8_t blue, uint8_t white = 0) __attribute__((always_inline)) : b(blue), g(green), r(red), w(white) {}
|
||||
|
||||
// Constructor from CRGB
|
||||
constexpr CRGBW(CRGB rgb) __attribute__((always_inline)) : b(rgb.b), g(rgb.g), r(rgb.r), w(0) {}
|
||||
|
||||
// Access as an array
|
||||
inline const uint8_t& operator[] (uint8_t x) const __attribute__((always_inline)) { return raw[x]; }
|
||||
|
||||
// Assignment from 32-bit color
|
||||
inline CRGBW& operator=(uint32_t color) __attribute__((always_inline)) { color32 = color; return *this; }
|
||||
|
||||
// Assignment from r, g, b, w
|
||||
inline CRGBW& operator=(const CRGB& rgb) __attribute__((always_inline)) { b = rgb.b; g = rgb.g; r = rgb.r; w = 0; return *this; }
|
||||
|
||||
// Conversion operator to uint32_t
|
||||
inline operator uint32_t() const __attribute__((always_inline)) {
|
||||
return color32;
|
||||
}
|
||||
/*
|
||||
// Conversion operator to CRGB
|
||||
inline operator CRGB() const __attribute__((always_inline)) {
|
||||
return CRGB(r, g, b);
|
||||
}
|
||||
|
||||
CRGBW& scale32 (uint8_t scaledown) // 32bit math
|
||||
{
|
||||
if (color32 == 0) return *this; // 2 extra instructions, worth it if called a lot on black (which probably is true) adding check if scaledown is zero adds much more overhead as its 8bit
|
||||
uint32_t scale = scaledown + 1;
|
||||
uint32_t rb = (((color32 & 0x00FF00FF) * scale) >> 8) & 0x00FF00FF; // scale red and blue
|
||||
uint32_t wg = (((color32 & 0xFF00FF00) >> 8) * scale) & 0xFF00FF00; // scale white and green
|
||||
color32 = rb | wg;
|
||||
return *this;
|
||||
}*/
|
||||
|
||||
};
|
||||
|
||||
struct CHSV32 { // 32bit HSV color with 16bit hue for more accurate conversions
|
||||
union {
|
||||
struct {
|
||||
uint16_t h; // hue
|
||||
uint8_t s; // saturation
|
||||
uint8_t v; // value
|
||||
};
|
||||
uint32_t raw; // 32bit access
|
||||
};
|
||||
inline CHSV32() __attribute__((always_inline)) = default; // default constructor
|
||||
|
||||
/// Allow construction from hue, saturation, and value
|
||||
/// @param ih input hue
|
||||
/// @param is input saturation
|
||||
/// @param iv input value
|
||||
inline CHSV32(uint16_t ih, uint8_t is, uint8_t iv) __attribute__((always_inline)) // constructor from 16bit h, s, v
|
||||
: h(ih), s(is), v(iv) {}
|
||||
inline CHSV32(uint8_t ih, uint8_t is, uint8_t iv) __attribute__((always_inline)) // constructor from 8bit h, s, v
|
||||
: h((uint16_t)ih << 8), s(is), v(iv) {}
|
||||
inline CHSV32(const CHSV& chsv) __attribute__((always_inline)) // constructor from CHSV
|
||||
: h((uint16_t)chsv.h << 8), s(chsv.s), v(chsv.v) {}
|
||||
inline operator CHSV() const { return CHSV((uint8_t)(h >> 8), s, v); } // typecast to CHSV
|
||||
};
|
||||
extern bool gammaCorrectCol;
|
||||
// similar to NeoPixelBus NeoGammaTableMethod but allows dynamic changes (superseded by NPB::NeoGammaDynamicTableMethod)
|
||||
class NeoGammaWLEDMethod {
|
||||
public:
|
||||
[[gnu::hot]] static uint8_t Correct(uint8_t value); // apply Gamma to single channel
|
||||
[[gnu::hot]] static uint32_t inverseGamma32(uint32_t color); // apply inverse Gamma to RGBW32 color
|
||||
static void calcGammaTable(float gamma); // re-calculates & fills gamma tables
|
||||
static inline uint8_t rawGamma8(uint8_t val) { return gammaT[val]; } // get value from Gamma table (WLED specific, not used by NPB)
|
||||
static inline uint8_t rawInverseGamma8(uint8_t val) { return gammaT_inv[val]; } // get value from inverse Gamma table (WLED specific, not used by NPB)
|
||||
static inline uint32_t Correct32(uint32_t color) { // apply Gamma to RGBW32 color (WLED specific, not used by NPB)
|
||||
if (!gammaCorrectCol) return color; // no gamma correction
|
||||
uint8_t w = byte(color>>24), r = byte(color>>16), g = byte(color>>8), b = byte(color); // extract r, g, b, w channels
|
||||
w = gammaT[w]; r = gammaT[r]; g = gammaT[g]; b = gammaT[b];
|
||||
return (uint32_t(w) << 24) | (uint32_t(r) << 16) | (uint32_t(g) << 8) | uint32_t(b);
|
||||
}
|
||||
private:
|
||||
static uint8_t gammaT[];
|
||||
static uint8_t gammaT_inv[];
|
||||
};
|
||||
#define gamma32(c) NeoGammaWLEDMethod::Correct32(c)
|
||||
#define gamma8(c) NeoGammaWLEDMethod::rawGamma8(c)
|
||||
#define gamma32inv(c) NeoGammaWLEDMethod::inverseGamma32(c)
|
||||
#define gamma8inv(c) NeoGammaWLEDMethod::rawInverseGamma8(c)
|
||||
[[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 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);
|
||||
void rgb2hsv(const uint32_t rgb, CHSV32& hsv);
|
||||
inline CHSV rgb2hsv(const CRGB c) { CHSV32 hsv; rgb2hsv((uint32_t((byte(c.r) << 16) | (byte(c.g) << 8) | (byte(c.b)))), hsv); return CHSV(hsv); } // CRGB to hsv
|
||||
void colorKtoRGB(uint16_t kelvin, byte* rgb);
|
||||
void colorCTtoRGB(uint16_t mired, byte* rgb); //white spectrum to rgb
|
||||
void colorXYtoRGB(float x, float y, byte* rgb); // only defined if huesync disabled TODO
|
||||
void colorRGBtoXY(const byte* rgb, float* xy); // only defined if huesync disabled TODO
|
||||
void colorFromDecOrHexString(byte* rgb, const char* in);
|
||||
bool colorFromHexString(byte* rgb, const char* in);
|
||||
uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
|
||||
uint16_t approximateKelvinFromRGB(uint32_t rgb);
|
||||
void setRandomColor(byte* rgb);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// palettes
|
||||
extern const TProgmemRGBPalette16* const fastledPalettes[];
|
||||
extern const uint8_t* const gGradientPalettes[];
|
||||
#endif
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1042,7 +1042,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 +1065,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()});`;
|
||||
}
|
||||
@@ -3086,9 +3088,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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
@@ -107,7 +107,6 @@ Y: <input name="P${i}Y" type="number" min="0" max="255" value="0" oninput="UI()"
|
||||
Sf[`P${p}H`].value = ph;
|
||||
}
|
||||
}
|
||||
UI(); // Update the preview after generating panels
|
||||
}
|
||||
|
||||
function expand(o,i)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
@@ -940,6 +908,7 @@ Swap: <select id="xw${s}" name="XW${s}">
|
||||
<option value="3">None (not recommended)</option>
|
||||
</select><br>
|
||||
Use harmonic <i>Random Cycle</i> palette: <input type="checkbox" name="TH"><br>
|
||||
Use "rainbow" color wheel: <input type="checkbox" name="RW"><br>
|
||||
Target refresh rate: <input type="number" class="s" min="0" max="250" name="FR" oninput="UI()" required> FPS
|
||||
<div id="fpsNone" class="warn" style="display: none;">⚠ Unlimited FPS Mode is experimental ⚠<br></div>
|
||||
<div id="fpsHigh" class="warn" style="display: none;">⚠ High FPS Mode is experimental.<br></div>
|
||||
|
||||
@@ -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>
|
||||
@@ -76,7 +76,7 @@
|
||||
A huge thank you to everyone who helped me create WLED!<br><br>
|
||||
(c) 2016-2024 Christian Schwinne <br>
|
||||
<i>Licensed under the <a href="https://github.com/wled-dev/WLED/blob/main/LICENSE" target="_blank">EUPL v1.2 license</a></i><br><br>
|
||||
Installed version: <span class="sip">WLED ##VERSION##</span><hr>
|
||||
Server message: <span class="sip"> Response error! </span><hr>
|
||||
<div id="toast"></div>
|
||||
<button type="button" onclick="B()">Back</button><button type="submit">Save</button>
|
||||
</form>
|
||||
|
||||
@@ -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;} }
|
||||
|
||||
@@ -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"];
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -258,10 +258,10 @@ Static subnet mask:<br>
|
||||
<h3>Ethernet Type</h3>
|
||||
<select name="ETH">
|
||||
<option value="0">None</option>
|
||||
<option value="6">IoTorero/ESP32Deux/RGB2Go</option>
|
||||
<option value="9">ABC! WLED V43 & compatible</option>
|
||||
<option value="2">ESP32-POE</option>
|
||||
<option value="11">ESP32-POE-WROVER</option>
|
||||
<option value="6">ESP32Deux/RGB2Go</option>
|
||||
<option value="7">KIT-VE</option>
|
||||
<option value="12">LILYGO T-POE Pro</option>
|
||||
<option value="8">QuinLED-Dig-Octa & T-ETH-POE</option>
|
||||
|
||||
@@ -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;
|
||||
@@ -27,7 +27,7 @@
|
||||
<body onload="GetV()">
|
||||
<h2>WLED Software Update</h2>
|
||||
<form method='POST' action='./update' id='upd' enctype='multipart/form-data' onsubmit="toggle('upd')">
|
||||
Installed version: <span class="sip">WLED ##VERSION##</span><br>
|
||||
Installed version: <span class="sip">##VERSION##</span><br>
|
||||
Download the latest binary: <a href="https://github.com/wled-dev/WLED/releases" target="_blank"
|
||||
style="vertical-align: text-bottom; display: inline-flex;">
|
||||
<img src="https://img.shields.io/github/release/wled-dev/WLED.svg?style=flat-square"></a><br>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -199,7 +191,7 @@ void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8
|
||||
// only change brightness if value changed
|
||||
if (bri != e131_data[dataOffset]) {
|
||||
bri = e131_data[dataOffset];
|
||||
strip.setBrightness(bri, false);
|
||||
strip.setBrightness(scaledBri(bri), false);
|
||||
stateUpdated(CALL_MODE_WS_SEND);
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -24,11 +24,6 @@ void handleIO();
|
||||
void IRAM_ATTR touchButtonISR();
|
||||
|
||||
//cfg.cpp
|
||||
bool backupConfig();
|
||||
bool restoreConfig();
|
||||
bool verifyConfig();
|
||||
bool configBackupExists();
|
||||
void resetConfig();
|
||||
bool deserializeConfig(JsonObject doc, bool fromFS = false);
|
||||
bool deserializeConfigFromFS();
|
||||
bool deserializeConfigSec();
|
||||
@@ -74,6 +69,133 @@ typedef struct WiFiConfig {
|
||||
}
|
||||
} wifi_config;
|
||||
|
||||
//colors.cpp
|
||||
#define ColorFromPalette ColorFromPaletteWLED // override fastled version
|
||||
|
||||
// CRGBW can be used to manipulate 32bit colors faster. However: if it is passed to functions, it adds overhead compared to a uint32_t color
|
||||
// use with caution and pay attention to flash size. Usually converting a uint32_t to CRGBW to extract r, g, b, w values is slower than using bitshifts
|
||||
// it can be useful to avoid back and forth conversions between uint32_t and fastled CRGB
|
||||
struct CRGBW {
|
||||
union {
|
||||
uint32_t color32; // Access as a 32-bit value (0xWWRRGGBB)
|
||||
struct {
|
||||
uint8_t b;
|
||||
uint8_t g;
|
||||
uint8_t r;
|
||||
uint8_t w;
|
||||
};
|
||||
uint8_t raw[4]; // Access as an array in the order B, G, R, W
|
||||
};
|
||||
|
||||
// Default constructor
|
||||
inline CRGBW() __attribute__((always_inline)) = default;
|
||||
|
||||
// Constructor from a 32-bit color (0xWWRRGGBB)
|
||||
constexpr CRGBW(uint32_t color) __attribute__((always_inline)) : color32(color) {}
|
||||
|
||||
// Constructor with r, g, b, w values
|
||||
constexpr CRGBW(uint8_t red, uint8_t green, uint8_t blue, uint8_t white = 0) __attribute__((always_inline)) : b(blue), g(green), r(red), w(white) {}
|
||||
|
||||
// Constructor from CRGB
|
||||
constexpr CRGBW(CRGB rgb) __attribute__((always_inline)) : b(rgb.b), g(rgb.g), r(rgb.r), w(0) {}
|
||||
|
||||
// Access as an array
|
||||
inline const uint8_t& operator[] (uint8_t x) const __attribute__((always_inline)) { return raw[x]; }
|
||||
|
||||
// Assignment from 32-bit color
|
||||
inline CRGBW& operator=(uint32_t color) __attribute__((always_inline)) { color32 = color; return *this; }
|
||||
|
||||
// Assignment from r, g, b, w
|
||||
inline CRGBW& operator=(const CRGB& rgb) __attribute__((always_inline)) { b = rgb.b; g = rgb.g; r = rgb.r; w = 0; return *this; }
|
||||
|
||||
// Conversion operator to uint32_t
|
||||
inline operator uint32_t() const __attribute__((always_inline)) {
|
||||
return color32;
|
||||
}
|
||||
/*
|
||||
// Conversion operator to CRGB
|
||||
inline operator CRGB() const __attribute__((always_inline)) {
|
||||
return CRGB(r, g, b);
|
||||
}
|
||||
|
||||
CRGBW& scale32 (uint8_t scaledown) // 32bit math
|
||||
{
|
||||
if (color32 == 0) return *this; // 2 extra instructions, worth it if called a lot on black (which probably is true) adding check if scaledown is zero adds much more overhead as its 8bit
|
||||
uint32_t scale = scaledown + 1;
|
||||
uint32_t rb = (((color32 & 0x00FF00FF) * scale) >> 8) & 0x00FF00FF; // scale red and blue
|
||||
uint32_t wg = (((color32 & 0xFF00FF00) >> 8) * scale) & 0xFF00FF00; // scale white and green
|
||||
color32 = rb | wg;
|
||||
return *this;
|
||||
}*/
|
||||
|
||||
};
|
||||
|
||||
struct CHSV32 { // 32bit HSV color with 16bit hue for more accurate conversions
|
||||
union {
|
||||
struct {
|
||||
uint16_t h; // hue
|
||||
uint8_t s; // saturation
|
||||
uint8_t v; // value
|
||||
};
|
||||
uint32_t raw; // 32bit access
|
||||
};
|
||||
inline CHSV32() __attribute__((always_inline)) = default; // default constructor
|
||||
|
||||
/// Allow construction from hue, saturation, and value
|
||||
/// @param ih input hue
|
||||
/// @param is input saturation
|
||||
/// @param iv input value
|
||||
inline CHSV32(uint16_t ih, uint8_t is, uint8_t iv) __attribute__((always_inline)) // constructor from 16bit h, s, v
|
||||
: h(ih), s(is), v(iv) {}
|
||||
inline CHSV32(uint8_t ih, uint8_t is, uint8_t iv) __attribute__((always_inline)) // constructor from 8bit h, s, v
|
||||
: h((uint16_t)ih << 8), s(is), v(iv) {}
|
||||
inline CHSV32(const CHSV& chsv) __attribute__((always_inline)) // constructor from CHSV
|
||||
: h((uint16_t)chsv.h << 8), s(chsv.s), v(chsv.v) {}
|
||||
inline operator CHSV() const { return CHSV((uint8_t)(h >> 8), s, v); } // typecast to CHSV
|
||||
};
|
||||
// similar to NeoPixelBus NeoGammaTableMethod but allows dynamic changes (superseded by NPB::NeoGammaDynamicTableMethod)
|
||||
class NeoGammaWLEDMethod {
|
||||
public:
|
||||
[[gnu::hot]] static uint8_t Correct(uint8_t value); // apply Gamma to single channel
|
||||
[[gnu::hot]] static uint32_t Correct32(uint32_t color); // apply Gamma to RGBW32 color (WLED specific, not used by NPB)
|
||||
[[gnu::hot]] static uint32_t inverseGamma32(uint32_t color); // apply inverse Gamma to RGBW32 color
|
||||
static void calcGammaTable(float gamma); // re-calculates & fills gamma tables
|
||||
static inline uint8_t rawGamma8(uint8_t val) { return gammaT[val]; } // get value from Gamma table (WLED specific, not used by NPB)
|
||||
static inline uint8_t rawInverseGamma8(uint8_t val) { return gammaT_inv[val]; } // get value from inverse Gamma table (WLED specific, not used by NPB)
|
||||
private:
|
||||
static uint8_t gammaT[];
|
||||
static uint8_t gammaT_inv[];
|
||||
};
|
||||
#define gamma32(c) NeoGammaWLEDMethod::Correct32(c)
|
||||
#define gamma8(c) NeoGammaWLEDMethod::rawGamma8(c)
|
||||
#define gamma32inv(c) NeoGammaWLEDMethod::inverseGamma32(c)
|
||||
#define gamma8inv(c) NeoGammaWLEDMethod::rawInverseGamma8(c)
|
||||
[[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 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);
|
||||
void rgb2hsv(const uint32_t rgb, CHSV32& hsv);
|
||||
inline CHSV rgb2hsv(const CRGB c) { CHSV32 hsv; rgb2hsv((uint32_t((byte(c.r) << 16) | (byte(c.g) << 8) | (byte(c.b)))), hsv); return CHSV(hsv); } // CRGB to hsv
|
||||
void colorKtoRGB(uint16_t kelvin, byte* rgb);
|
||||
void colorCTtoRGB(uint16_t mired, byte* rgb); //white spectrum to rgb
|
||||
void colorXYtoRGB(float x, float y, byte* rgb); // only defined if huesync disabled TODO
|
||||
void colorRGBtoXY(const byte* rgb, float* xy); // only defined if huesync disabled TODO
|
||||
void colorFromDecOrHexString(byte* rgb, const char* in);
|
||||
bool colorFromHexString(byte* rgb, const char* in);
|
||||
uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
|
||||
uint16_t approximateKelvinFromRGB(uint32_t rgb);
|
||||
void setRandomColor(byte* rgb);
|
||||
|
||||
//dmx_output.cpp
|
||||
void initDMXOutput();
|
||||
void handleDMXOutput();
|
||||
@@ -101,12 +223,6 @@ inline bool writeObjectToFileUsingId(const String &file, uint16_t id, const Json
|
||||
inline bool writeObjectToFile(const String &file, const char* key, const JsonDocument* content) { return writeObjectToFile(file.c_str(), key, content); };
|
||||
inline bool readObjectFromFileUsingId(const String &file, uint16_t id, JsonDocument* dest, const JsonDocument* filter = nullptr) { return readObjectFromFileUsingId(file.c_str(), id, dest); };
|
||||
inline bool readObjectFromFile(const String &file, const char* key, JsonDocument* dest, const JsonDocument* filter = nullptr) { return readObjectFromFile(file.c_str(), key, dest); };
|
||||
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();
|
||||
|
||||
//hue.cpp
|
||||
void handleHue();
|
||||
@@ -317,7 +433,6 @@ class Usermod {
|
||||
virtual void onMqttConnect(bool sessionPresent) {} // fired when MQTT connection is established (so usermod can subscribe)
|
||||
virtual bool onMqttMessage(char* topic, char* payload) { return false; } // fired upon MQTT message received (wled topic)
|
||||
virtual bool onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t len) { return false; } // fired upon ESP-NOW message received
|
||||
virtual bool onUdpPacket(uint8_t* payload, size_t len) { return false; } //fired upon UDP packet received
|
||||
virtual void onUpdateBegin(bool) {} // fired prior to and after unsuccessful firmware update
|
||||
virtual void onStateChange(uint8_t mode) {} // fired upon WLED state change
|
||||
virtual uint16_t getId() {return USERMOD_ID_UNSPECIFIED;}
|
||||
@@ -357,7 +472,6 @@ namespace UsermodManager {
|
||||
#ifndef WLED_DISABLE_ESPNOW
|
||||
bool onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t len);
|
||||
#endif
|
||||
bool onUdpPacket(uint8_t* payload, size_t len);
|
||||
void onUpdateBegin(bool);
|
||||
void onStateChange(uint8_t);
|
||||
Usermod* lookup(uint16_t mod_id);
|
||||
@@ -367,11 +481,6 @@ namespace UsermodManager {
|
||||
// Register usermods by building a static list via a linker section
|
||||
#define REGISTER_USERMOD(x) Usermod* const um_##x __attribute__((__section__(".dtors.tbl.usermods.1"), used)) = &x
|
||||
|
||||
//usermod.cpp
|
||||
void userSetup();
|
||||
void userConnected();
|
||||
void userLoop();
|
||||
|
||||
//util.cpp
|
||||
#ifdef ESP8266
|
||||
#define HW_RND_REGISTER RANDOM_REG32
|
||||
@@ -436,49 +545,36 @@ 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
|
||||
void bootloopCheckOTA(); // swap boot image if bootloop is detected instead of restoring config
|
||||
#endif
|
||||
// RAII guard class for the JSON Buffer lock
|
||||
// Modeled after std::lock_guard
|
||||
class JSONBufferGuard {
|
||||
|
||||
163
wled00/file.cpp
163
wled00/file.cpp
@@ -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) {
|
||||
@@ -439,162 +439,3 @@ bool handleFileRead(AsyncWebServerRequest* request, String path){
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// copy a file, delete destination file if incomplete to prevent corrupted files
|
||||
bool copyFile(const char* src_path, const char* dst_path) {
|
||||
DEBUG_PRINTF("copyFile from %s to %s\n", src_path, dst_path);
|
||||
if(!WLED_FS.exists(src_path)) {
|
||||
DEBUG_PRINTLN(F("file not found"));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = true; // is set to false on error
|
||||
File src = WLED_FS.open(src_path, "r");
|
||||
File dst = WLED_FS.open(dst_path, "w");
|
||||
|
||||
if (src && dst) {
|
||||
uint8_t buf[128]; // copy file in 128-byte blocks
|
||||
while (src.available() > 0) {
|
||||
size_t bytesRead = src.read(buf, sizeof(buf));
|
||||
if (bytesRead == 0) {
|
||||
success = false;
|
||||
break; // error, no data read
|
||||
}
|
||||
size_t bytesWritten = dst.write(buf, bytesRead);
|
||||
if (bytesWritten != bytesRead) {
|
||||
success = false;
|
||||
break; // error, not all data written
|
||||
}
|
||||
}
|
||||
} else {
|
||||
success = false; // error, could not open files
|
||||
}
|
||||
if(src) src.close();
|
||||
if(dst) dst.close();
|
||||
if (!success) {
|
||||
DEBUG_PRINTLN(F("copy failed"));
|
||||
WLED_FS.remove(dst_path); // delete incomplete file
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
// compare two files, return true if identical
|
||||
bool compareFiles(const char* path1, const char* path2) {
|
||||
DEBUG_PRINTF("compareFile %s and %s\n", path1, path2);
|
||||
if (!WLED_FS.exists(path1) || !WLED_FS.exists(path2)) {
|
||||
DEBUG_PRINTLN(F("file not found"));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool identical = true; // set to false on mismatch
|
||||
File f1 = WLED_FS.open(path1, "r");
|
||||
File f2 = WLED_FS.open(path2, "r");
|
||||
|
||||
if (f1 && f2) {
|
||||
uint8_t buf1[128], buf2[128];
|
||||
while (f1.available() > 0 || f2.available() > 0) {
|
||||
size_t len1 = f1.read(buf1, sizeof(buf1));
|
||||
size_t len2 = f2.read(buf2, sizeof(buf2));
|
||||
|
||||
if (len1 != len2) {
|
||||
identical = false;
|
||||
break; // files differ in size or read failed
|
||||
}
|
||||
|
||||
if (memcmp(buf1, buf2, len1) != 0) {
|
||||
identical = false;
|
||||
break; // files differ in content
|
||||
}
|
||||
}
|
||||
} else {
|
||||
identical = false; // error opening files
|
||||
}
|
||||
|
||||
if (f1) f1.close();
|
||||
if (f2) f2.close();
|
||||
return identical;
|
||||
}
|
||||
|
||||
static const char s_backup_fmt[] PROGMEM = "/bkp.%s";
|
||||
|
||||
bool backupFile(const char* filename) {
|
||||
DEBUG_PRINTF("backup %s \n", filename);
|
||||
if (!validateJsonFile(filename)) {
|
||||
DEBUG_PRINTLN(F("broken file"));
|
||||
return false;
|
||||
}
|
||||
char backupname[32];
|
||||
snprintf_P(backupname, sizeof(backupname), s_backup_fmt, filename + 1); // skip leading '/' in filename
|
||||
|
||||
if (copyFile(filename, backupname)) {
|
||||
DEBUG_PRINTLN(F("backup ok"));
|
||||
return true;
|
||||
}
|
||||
DEBUG_PRINTLN(F("backup failed"));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool restoreFile(const char* filename) {
|
||||
DEBUG_PRINTF("restore %s \n", filename);
|
||||
char backupname[32];
|
||||
snprintf_P(backupname, sizeof(backupname), s_backup_fmt, filename + 1); // skip leading '/' in filename
|
||||
|
||||
if (!WLED_FS.exists(backupname)) {
|
||||
DEBUG_PRINTLN(F("no backup found"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!validateJsonFile(backupname)) {
|
||||
DEBUG_PRINTLN(F("broken backup"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (copyFile(backupname, filename)) {
|
||||
DEBUG_PRINTLN(F("restore ok"));
|
||||
return true;
|
||||
}
|
||||
DEBUG_PRINTLN(F("restore failed"));
|
||||
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");
|
||||
if (!file) return false;
|
||||
StaticJsonDocument<0> doc, filter; // https://arduinojson.org/v6/how-to/validate-json/
|
||||
bool result = deserializeJson(doc, file, DeserializationOption::Filter(filter)) == DeserializationError::Ok;
|
||||
file.close();
|
||||
if (!result) {
|
||||
DEBUG_PRINTF_P(PSTR("Invalid JSON file %s\n"), filename);
|
||||
} else {
|
||||
DEBUG_PRINTF_P(PSTR("Valid JSON file %s\n"), filename);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// print contents of all files in root dir to Serial except wsec files
|
||||
void dumpFilesToSerial() {
|
||||
File rootdir = WLED_FS.open("/", "r");
|
||||
File rootfile = rootdir.openNextFile();
|
||||
while (rootfile) {
|
||||
size_t len = strlen(rootfile.name());
|
||||
// skip files starting with "wsec" and dont end in .json
|
||||
if (strncmp(rootfile.name(), "wsec", 4) != 0 && len >= 6 && strcmp(rootfile.name() + len - 5, ".json") == 0) {
|
||||
Serial.println(rootfile.name());
|
||||
while (rootfile.available()) {
|
||||
Serial.write(rootfile.read());
|
||||
}
|
||||
Serial.println();
|
||||
Serial.println();
|
||||
}
|
||||
rootfile.close();
|
||||
rootfile = rootdir.openNextFile();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ void drawPixelCallback(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t
|
||||
// set multiple pixels if upscaling
|
||||
for (int16_t i = 0; i < (activeSeg->width()+(gifWidth-1)) / gifWidth; i++) {
|
||||
for (int16_t j = 0; j < (activeSeg->height()+(gifHeight-1)) / gifHeight; j++) {
|
||||
activeSeg->setPixelColorXY(outX + i, outY + j, red, green, blue);
|
||||
activeSeg->setPixelColorXY(outX + i, outY + j, gamma8(red), gamma8(green), gamma8(blue));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -310,7 +312,7 @@ static bool deserializeSegment(JsonObject elem, byte it, byte presetId = 0)
|
||||
jsonTransitionOnce = true;
|
||||
if (seg.isInTransition()) seg.startTransition(0); // setting transition time to 0 will stop transition in next frame
|
||||
strip.setTransition(0);
|
||||
strip.setBrightness(bri, true);
|
||||
strip.setBrightness(scaledBri(bri), true);
|
||||
|
||||
// freeze and init to black
|
||||
if (!seg.freeze) {
|
||||
@@ -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 {
|
||||
|
||||
@@ -57,7 +57,7 @@ void toggleOnOff()
|
||||
//scales the brightness with the briMultiplier factor
|
||||
byte scaledBri(byte in)
|
||||
{
|
||||
unsigned val = ((unsigned)in*briMultiplier)/100;
|
||||
unsigned val = ((uint16_t)in*briMultiplier)/100;
|
||||
if (val > 255) val = 255;
|
||||
return (byte)val;
|
||||
}
|
||||
@@ -68,7 +68,7 @@ void applyBri() {
|
||||
if (realtimeOverride || !(realtimeMode && arlsForceMaxBri))
|
||||
{
|
||||
//DEBUG_PRINTF_P(PSTR("Applying strip brightness: %d (%d,%d)\n"), (int)briT, (int)bri, (int)briOld);
|
||||
strip.setBrightness(briT);
|
||||
strip.setBrightness(scaledBri(briT));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
5
wled00/palettes.cpp → wled00/palettes.h
Normal file → Executable file
5
wled00/palettes.cpp → wled00/palettes.h
Normal file → Executable 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
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -351,6 +351,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
|
||||
t = request->arg(F("TP")).toInt();
|
||||
randomPaletteChangeTime = MIN(255,MAX(1,t));
|
||||
useHarmonicRandomPalette = request->hasArg(F("TH"));
|
||||
useRainbowWheel = request->hasArg(F("RW"));
|
||||
|
||||
nightlightTargetBri = request->arg(F("TB")).toInt();
|
||||
t = request->arg(F("TL")).toInt();
|
||||
|
||||
877
wled00/src/dependencies/async-mqtt-client/AsyncMqttClient.cpp
Normal file
877
wled00/src/dependencies/async-mqtt-client/AsyncMqttClient.cpp
Normal file
@@ -0,0 +1,877 @@
|
||||
#include "AsyncMqttClient.hpp"
|
||||
|
||||
AsyncMqttClient::AsyncMqttClient()
|
||||
: _connected(false)
|
||||
, _connectPacketNotEnoughSpace(false)
|
||||
, _disconnectFlagged(false)
|
||||
, _tlsBadFingerprint(false)
|
||||
, _lastClientActivity(0)
|
||||
, _lastServerActivity(0)
|
||||
, _lastPingRequestTime(0)
|
||||
, _host(nullptr)
|
||||
, _useIp(false)
|
||||
#if ASYNC_TCP_SSL_ENABLED
|
||||
, _secure(false)
|
||||
#endif
|
||||
, _port(0)
|
||||
, _keepAlive(15)
|
||||
, _cleanSession(true)
|
||||
, _clientId(nullptr)
|
||||
, _username(nullptr)
|
||||
, _password(nullptr)
|
||||
, _willTopic(nullptr)
|
||||
, _willPayload(nullptr)
|
||||
, _willPayloadLength(0)
|
||||
, _willQos(0)
|
||||
, _willRetain(false)
|
||||
, _parsingInformation { .bufferState = AsyncMqttClientInternals::BufferState::NONE }
|
||||
, _currentParsedPacket(nullptr)
|
||||
, _remainingLengthBufferPosition(0)
|
||||
, _nextPacketId(1) {
|
||||
_client.onConnect([](void* obj, AsyncClient* c) { (static_cast<AsyncMqttClient*>(obj))->_onConnect(c); }, this);
|
||||
_client.onDisconnect([](void* obj, AsyncClient* c) { (static_cast<AsyncMqttClient*>(obj))->_onDisconnect(c); }, this);
|
||||
_client.onError([](void* obj, AsyncClient* c, int8_t error) { (static_cast<AsyncMqttClient*>(obj))->_onError(c, error); }, this);
|
||||
_client.onTimeout([](void* obj, AsyncClient* c, uint32_t time) { (static_cast<AsyncMqttClient*>(obj))->_onTimeout(c, time); }, this);
|
||||
_client.onAck([](void* obj, AsyncClient* c, size_t len, uint32_t time) { (static_cast<AsyncMqttClient*>(obj))->_onAck(c, len, time); }, this);
|
||||
_client.onData([](void* obj, AsyncClient* c, void* data, size_t len) { (static_cast<AsyncMqttClient*>(obj))->_onData(c, static_cast<char*>(data), len); }, this);
|
||||
_client.onPoll([](void* obj, AsyncClient* c) { (static_cast<AsyncMqttClient*>(obj))->_onPoll(c); }, this);
|
||||
|
||||
#ifdef ESP32
|
||||
sprintf(_generatedClientId, "esp32%06x", (uint32_t)ESP.getEfuseMac());
|
||||
_xSemaphore = xSemaphoreCreateMutex();
|
||||
#elif defined(ESP8266)
|
||||
sprintf(_generatedClientId, "esp8266%06x", (uint32_t)ESP.getChipId());
|
||||
#endif
|
||||
_clientId = _generatedClientId;
|
||||
|
||||
setMaxTopicLength(128);
|
||||
}
|
||||
|
||||
AsyncMqttClient::~AsyncMqttClient() {
|
||||
delete _currentParsedPacket;
|
||||
delete[] _parsingInformation.topicBuffer;
|
||||
#ifdef ESP32
|
||||
vSemaphoreDelete(_xSemaphore);
|
||||
#endif
|
||||
}
|
||||
|
||||
AsyncMqttClient& AsyncMqttClient::setKeepAlive(uint16_t keepAlive) {
|
||||
_keepAlive = keepAlive;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncMqttClient& AsyncMqttClient::setClientId(const char* clientId) {
|
||||
_clientId = clientId;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncMqttClient& AsyncMqttClient::setCleanSession(bool cleanSession) {
|
||||
_cleanSession = cleanSession;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncMqttClient& AsyncMqttClient::setMaxTopicLength(uint16_t maxTopicLength) {
|
||||
_parsingInformation.maxTopicLength = maxTopicLength;
|
||||
delete[] _parsingInformation.topicBuffer;
|
||||
_parsingInformation.topicBuffer = new char[maxTopicLength + 1];
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncMqttClient& AsyncMqttClient::setCredentials(const char* username, const char* password) {
|
||||
_username = username;
|
||||
_password = password;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncMqttClient& AsyncMqttClient::setWill(const char* topic, uint8_t qos, bool retain, const char* payload, size_t length) {
|
||||
_willTopic = topic;
|
||||
_willQos = qos;
|
||||
_willRetain = retain;
|
||||
_willPayload = payload;
|
||||
_willPayloadLength = length;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncMqttClient& AsyncMqttClient::setServer(IPAddress ip, uint16_t port) {
|
||||
_useIp = true;
|
||||
_ip = ip;
|
||||
_port = port;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncMqttClient& AsyncMqttClient::setServer(const char* host, uint16_t port) {
|
||||
_useIp = false;
|
||||
_host = host;
|
||||
_port = port;
|
||||
return *this;
|
||||
}
|
||||
|
||||
#if ASYNC_TCP_SSL_ENABLED
|
||||
AsyncMqttClient& AsyncMqttClient::setSecure(bool secure) {
|
||||
_secure = secure;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncMqttClient& AsyncMqttClient::addServerFingerprint(const uint8_t* fingerprint) {
|
||||
std::array<uint8_t, SHA1_SIZE> newFingerprint;
|
||||
memcpy(newFingerprint.data(), fingerprint, SHA1_SIZE);
|
||||
_secureServerFingerprints.push_back(newFingerprint);
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
|
||||
AsyncMqttClient& AsyncMqttClient::onConnect(AsyncMqttClientInternals::OnConnectUserCallback callback) {
|
||||
_onConnectUserCallbacks.push_back(callback);
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncMqttClient& AsyncMqttClient::onDisconnect(AsyncMqttClientInternals::OnDisconnectUserCallback callback) {
|
||||
_onDisconnectUserCallbacks.push_back(callback);
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncMqttClient& AsyncMqttClient::onSubscribe(AsyncMqttClientInternals::OnSubscribeUserCallback callback) {
|
||||
_onSubscribeUserCallbacks.push_back(callback);
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncMqttClient& AsyncMqttClient::onUnsubscribe(AsyncMqttClientInternals::OnUnsubscribeUserCallback callback) {
|
||||
_onUnsubscribeUserCallbacks.push_back(callback);
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncMqttClient& AsyncMqttClient::onMessage(AsyncMqttClientInternals::OnMessageUserCallback callback) {
|
||||
_onMessageUserCallbacks.push_back(callback);
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncMqttClient& AsyncMqttClient::onPublish(AsyncMqttClientInternals::OnPublishUserCallback callback) {
|
||||
_onPublishUserCallbacks.push_back(callback);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void AsyncMqttClient::_freeCurrentParsedPacket() {
|
||||
delete _currentParsedPacket;
|
||||
_currentParsedPacket = nullptr;
|
||||
}
|
||||
|
||||
void AsyncMqttClient::_clear() {
|
||||
_lastPingRequestTime = 0;
|
||||
_connected = false;
|
||||
_disconnectFlagged = false;
|
||||
_connectPacketNotEnoughSpace = false;
|
||||
_tlsBadFingerprint = false;
|
||||
_freeCurrentParsedPacket();
|
||||
|
||||
_pendingPubRels.clear();
|
||||
_pendingPubRels.shrink_to_fit();
|
||||
|
||||
_toSendAcks.clear();
|
||||
_toSendAcks.shrink_to_fit();
|
||||
|
||||
_nextPacketId = 1;
|
||||
_parsingInformation.bufferState = AsyncMqttClientInternals::BufferState::NONE;
|
||||
}
|
||||
|
||||
/* TCP */
|
||||
void AsyncMqttClient::_onConnect(AsyncClient* client) {
|
||||
(void)client;
|
||||
|
||||
#if ASYNC_TCP_SSL_ENABLED
|
||||
if (_secure && _secureServerFingerprints.size() > 0) {
|
||||
SSL* clientSsl = _client.getSSL();
|
||||
|
||||
bool sslFoundFingerprint = false;
|
||||
for (std::array<uint8_t, SHA1_SIZE> fingerprint : _secureServerFingerprints) {
|
||||
if (ssl_match_fingerprint(clientSsl, fingerprint.data()) == SSL_OK) {
|
||||
sslFoundFingerprint = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!sslFoundFingerprint) {
|
||||
_tlsBadFingerprint = true;
|
||||
_client.close(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
char fixedHeader[5];
|
||||
fixedHeader[0] = AsyncMqttClientInternals::PacketType.CONNECT;
|
||||
fixedHeader[0] = fixedHeader[0] << 4;
|
||||
fixedHeader[0] = fixedHeader[0] | AsyncMqttClientInternals::HeaderFlag.CONNECT_RESERVED;
|
||||
|
||||
uint16_t protocolNameLength = 4;
|
||||
char protocolNameLengthBytes[2];
|
||||
protocolNameLengthBytes[0] = protocolNameLength >> 8;
|
||||
protocolNameLengthBytes[1] = protocolNameLength & 0xFF;
|
||||
|
||||
char protocolLevel[1];
|
||||
protocolLevel[0] = 0x04;
|
||||
|
||||
char connectFlags[1];
|
||||
connectFlags[0] = 0;
|
||||
if (_cleanSession) connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.CLEAN_SESSION;
|
||||
if (_username != nullptr) connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.USERNAME;
|
||||
if (_password != nullptr) connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.PASSWORD;
|
||||
if (_willTopic != nullptr) {
|
||||
connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.WILL;
|
||||
if (_willRetain) connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.WILL_RETAIN;
|
||||
switch (_willQos) {
|
||||
case 0:
|
||||
connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.WILL_QOS0;
|
||||
break;
|
||||
case 1:
|
||||
connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.WILL_QOS1;
|
||||
break;
|
||||
case 2:
|
||||
connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.WILL_QOS2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
char keepAliveBytes[2];
|
||||
keepAliveBytes[0] = _keepAlive >> 8;
|
||||
keepAliveBytes[1] = _keepAlive & 0xFF;
|
||||
|
||||
uint16_t clientIdLength = strlen(_clientId);
|
||||
char clientIdLengthBytes[2];
|
||||
clientIdLengthBytes[0] = clientIdLength >> 8;
|
||||
clientIdLengthBytes[1] = clientIdLength & 0xFF;
|
||||
|
||||
// Optional fields
|
||||
uint16_t willTopicLength = 0;
|
||||
char willTopicLengthBytes[2];
|
||||
uint16_t willPayloadLength = _willPayloadLength;
|
||||
char willPayloadLengthBytes[2];
|
||||
if (_willTopic != nullptr) {
|
||||
willTopicLength = strlen(_willTopic);
|
||||
willTopicLengthBytes[0] = willTopicLength >> 8;
|
||||
willTopicLengthBytes[1] = willTopicLength & 0xFF;
|
||||
|
||||
if (_willPayload != nullptr && willPayloadLength == 0) willPayloadLength = strlen(_willPayload);
|
||||
|
||||
willPayloadLengthBytes[0] = willPayloadLength >> 8;
|
||||
willPayloadLengthBytes[1] = willPayloadLength & 0xFF;
|
||||
}
|
||||
|
||||
uint16_t usernameLength = 0;
|
||||
char usernameLengthBytes[2];
|
||||
if (_username != nullptr) {
|
||||
usernameLength = strlen(_username);
|
||||
usernameLengthBytes[0] = usernameLength >> 8;
|
||||
usernameLengthBytes[1] = usernameLength & 0xFF;
|
||||
}
|
||||
|
||||
uint16_t passwordLength = 0;
|
||||
char passwordLengthBytes[2];
|
||||
if (_password != nullptr) {
|
||||
passwordLength = strlen(_password);
|
||||
passwordLengthBytes[0] = passwordLength >> 8;
|
||||
passwordLengthBytes[1] = passwordLength & 0xFF;
|
||||
}
|
||||
|
||||
uint32_t remainingLength = 2 + protocolNameLength + 1 + 1 + 2 + 2 + clientIdLength; // always present
|
||||
if (_willTopic != nullptr) remainingLength += 2 + willTopicLength + 2 + willPayloadLength;
|
||||
if (_username != nullptr) remainingLength += 2 + usernameLength;
|
||||
if (_password != nullptr) remainingLength += 2 + passwordLength;
|
||||
uint8_t remainingLengthLength = AsyncMqttClientInternals::Helpers::encodeRemainingLength(remainingLength, fixedHeader + 1);
|
||||
|
||||
uint32_t neededSpace = 1 + remainingLengthLength;
|
||||
neededSpace += 2;
|
||||
neededSpace += protocolNameLength;
|
||||
neededSpace += 1;
|
||||
neededSpace += 1;
|
||||
neededSpace += 2;
|
||||
neededSpace += 2;
|
||||
neededSpace += clientIdLength;
|
||||
if (_willTopic != nullptr) {
|
||||
neededSpace += 2;
|
||||
neededSpace += willTopicLength;
|
||||
|
||||
neededSpace += 2;
|
||||
if (_willPayload != nullptr) neededSpace += willPayloadLength;
|
||||
}
|
||||
if (_username != nullptr) {
|
||||
neededSpace += 2;
|
||||
neededSpace += usernameLength;
|
||||
}
|
||||
if (_password != nullptr) {
|
||||
neededSpace += 2;
|
||||
neededSpace += passwordLength;
|
||||
}
|
||||
|
||||
SEMAPHORE_TAKE();
|
||||
if (_client.space() < neededSpace) {
|
||||
_connectPacketNotEnoughSpace = true;
|
||||
_client.close(true);
|
||||
SEMAPHORE_GIVE();
|
||||
return;
|
||||
}
|
||||
|
||||
_client.add(fixedHeader, 1 + remainingLengthLength);
|
||||
_client.add(protocolNameLengthBytes, 2);
|
||||
_client.add("MQTT", protocolNameLength);
|
||||
_client.add(protocolLevel, 1);
|
||||
_client.add(connectFlags, 1);
|
||||
_client.add(keepAliveBytes, 2);
|
||||
_client.add(clientIdLengthBytes, 2);
|
||||
_client.add(_clientId, clientIdLength);
|
||||
if (_willTopic != nullptr) {
|
||||
_client.add(willTopicLengthBytes, 2);
|
||||
_client.add(_willTopic, willTopicLength);
|
||||
|
||||
_client.add(willPayloadLengthBytes, 2);
|
||||
if (_willPayload != nullptr) _client.add(_willPayload, willPayloadLength);
|
||||
}
|
||||
if (_username != nullptr) {
|
||||
_client.add(usernameLengthBytes, 2);
|
||||
_client.add(_username, usernameLength);
|
||||
}
|
||||
if (_password != nullptr) {
|
||||
_client.add(passwordLengthBytes, 2);
|
||||
_client.add(_password, passwordLength);
|
||||
}
|
||||
_client.send();
|
||||
_lastClientActivity = millis();
|
||||
SEMAPHORE_GIVE();
|
||||
}
|
||||
|
||||
void AsyncMqttClient::_onDisconnect(AsyncClient* client) {
|
||||
(void)client;
|
||||
if (!_disconnectFlagged) {
|
||||
AsyncMqttClientDisconnectReason reason;
|
||||
|
||||
if (_connectPacketNotEnoughSpace) {
|
||||
reason = AsyncMqttClientDisconnectReason::ESP8266_NOT_ENOUGH_SPACE;
|
||||
} else if (_tlsBadFingerprint) {
|
||||
reason = AsyncMqttClientDisconnectReason::TLS_BAD_FINGERPRINT;
|
||||
} else {
|
||||
reason = AsyncMqttClientDisconnectReason::TCP_DISCONNECTED;
|
||||
}
|
||||
for (auto callback : _onDisconnectUserCallbacks) callback(reason);
|
||||
}
|
||||
_clear();
|
||||
}
|
||||
|
||||
void AsyncMqttClient::_onError(AsyncClient* client, int8_t error) {
|
||||
(void)client;
|
||||
(void)error;
|
||||
// _onDisconnect called anyway
|
||||
}
|
||||
|
||||
void AsyncMqttClient::_onTimeout(AsyncClient* client, uint32_t time) {
|
||||
(void)client;
|
||||
(void)time;
|
||||
// disconnection will be handled by ping/pong management
|
||||
}
|
||||
|
||||
void AsyncMqttClient::_onAck(AsyncClient* client, size_t len, uint32_t time) {
|
||||
(void)client;
|
||||
(void)len;
|
||||
(void)time;
|
||||
}
|
||||
|
||||
void AsyncMqttClient::_onData(AsyncClient* client, char* data, size_t len) {
|
||||
(void)client;
|
||||
size_t currentBytePosition = 0;
|
||||
char currentByte;
|
||||
do {
|
||||
switch (_parsingInformation.bufferState) {
|
||||
case AsyncMqttClientInternals::BufferState::NONE:
|
||||
currentByte = data[currentBytePosition++];
|
||||
_parsingInformation.packetType = currentByte >> 4;
|
||||
_parsingInformation.packetFlags = (currentByte << 4) >> 4;
|
||||
_parsingInformation.bufferState = AsyncMqttClientInternals::BufferState::REMAINING_LENGTH;
|
||||
_lastServerActivity = millis();
|
||||
switch (_parsingInformation.packetType) {
|
||||
case AsyncMqttClientInternals::PacketType.CONNACK:
|
||||
_currentParsedPacket = new AsyncMqttClientInternals::ConnAckPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onConnAck, this, std::placeholders::_1, std::placeholders::_2));
|
||||
break;
|
||||
case AsyncMqttClientInternals::PacketType.PINGRESP:
|
||||
_currentParsedPacket = new AsyncMqttClientInternals::PingRespPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onPingResp, this));
|
||||
break;
|
||||
case AsyncMqttClientInternals::PacketType.SUBACK:
|
||||
_currentParsedPacket = new AsyncMqttClientInternals::SubAckPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onSubAck, this, std::placeholders::_1, std::placeholders::_2));
|
||||
break;
|
||||
case AsyncMqttClientInternals::PacketType.UNSUBACK:
|
||||
_currentParsedPacket = new AsyncMqttClientInternals::UnsubAckPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onUnsubAck, this, std::placeholders::_1));
|
||||
break;
|
||||
case AsyncMqttClientInternals::PacketType.PUBLISH:
|
||||
_currentParsedPacket = new AsyncMqttClientInternals::PublishPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6, std::placeholders::_7, std::placeholders::_8, std::placeholders::_9), std::bind(&AsyncMqttClient::_onPublish, this, std::placeholders::_1, std::placeholders::_2));
|
||||
break;
|
||||
case AsyncMqttClientInternals::PacketType.PUBREL:
|
||||
_currentParsedPacket = new AsyncMqttClientInternals::PubRelPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onPubRel, this, std::placeholders::_1));
|
||||
break;
|
||||
case AsyncMqttClientInternals::PacketType.PUBACK:
|
||||
_currentParsedPacket = new AsyncMqttClientInternals::PubAckPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onPubAck, this, std::placeholders::_1));
|
||||
break;
|
||||
case AsyncMqttClientInternals::PacketType.PUBREC:
|
||||
_currentParsedPacket = new AsyncMqttClientInternals::PubRecPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onPubRec, this, std::placeholders::_1));
|
||||
break;
|
||||
case AsyncMqttClientInternals::PacketType.PUBCOMP:
|
||||
_currentParsedPacket = new AsyncMqttClientInternals::PubCompPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onPubComp, this, std::placeholders::_1));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case AsyncMqttClientInternals::BufferState::REMAINING_LENGTH:
|
||||
currentByte = data[currentBytePosition++];
|
||||
_remainingLengthBuffer[_remainingLengthBufferPosition++] = currentByte;
|
||||
if (currentByte >> 7 == 0) {
|
||||
_parsingInformation.remainingLength = AsyncMqttClientInternals::Helpers::decodeRemainingLength(_remainingLengthBuffer);
|
||||
_remainingLengthBufferPosition = 0;
|
||||
if (_parsingInformation.remainingLength > 0) {
|
||||
_parsingInformation.bufferState = AsyncMqttClientInternals::BufferState::VARIABLE_HEADER;
|
||||
} else {
|
||||
// PINGRESP is a special case where it has no variable header, so the packet ends right here
|
||||
_parsingInformation.bufferState = AsyncMqttClientInternals::BufferState::NONE;
|
||||
_onPingResp();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case AsyncMqttClientInternals::BufferState::VARIABLE_HEADER:
|
||||
_currentParsedPacket->parseVariableHeader(data, len, ¤tBytePosition);
|
||||
break;
|
||||
case AsyncMqttClientInternals::BufferState::PAYLOAD:
|
||||
_currentParsedPacket->parsePayload(data, len, ¤tBytePosition);
|
||||
break;
|
||||
default:
|
||||
currentBytePosition = len;
|
||||
}
|
||||
} while (currentBytePosition != len);
|
||||
}
|
||||
|
||||
void AsyncMqttClient::_onPoll(AsyncClient* client) {
|
||||
if (!_connected) return;
|
||||
|
||||
// if there is too much time the client has sent a ping request without a response, disconnect client to avoid half open connections
|
||||
if (_lastPingRequestTime != 0 && (millis() - _lastPingRequestTime) >= (_keepAlive * 1000 * 2)) {
|
||||
disconnect();
|
||||
return;
|
||||
// send ping to ensure the server will receive at least one message inside keepalive window
|
||||
} else if (_lastPingRequestTime == 0 && (millis() - _lastClientActivity) >= (_keepAlive * 1000 * 0.7)) {
|
||||
_sendPing();
|
||||
|
||||
// send ping to verify if the server is still there (ensure this is not a half connection)
|
||||
} else if (_connected && _lastPingRequestTime == 0 && (millis() - _lastServerActivity) >= (_keepAlive * 1000 * 0.7)) {
|
||||
_sendPing();
|
||||
}
|
||||
|
||||
// handle to send ack packets
|
||||
|
||||
_sendAcks();
|
||||
|
||||
// handle disconnect
|
||||
|
||||
if (_disconnectFlagged) {
|
||||
_sendDisconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/* MQTT */
|
||||
void AsyncMqttClient::_onPingResp() {
|
||||
_freeCurrentParsedPacket();
|
||||
_lastPingRequestTime = 0;
|
||||
}
|
||||
|
||||
void AsyncMqttClient::_onConnAck(bool sessionPresent, uint8_t connectReturnCode) {
|
||||
(void)sessionPresent;
|
||||
_freeCurrentParsedPacket();
|
||||
|
||||
if (connectReturnCode == 0) {
|
||||
_connected = true;
|
||||
for (auto callback : _onConnectUserCallbacks) callback(sessionPresent);
|
||||
} else {
|
||||
for (auto callback : _onDisconnectUserCallbacks) callback(static_cast<AsyncMqttClientDisconnectReason>(connectReturnCode));
|
||||
_disconnectFlagged = true;
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncMqttClient::_onSubAck(uint16_t packetId, char status) {
|
||||
_freeCurrentParsedPacket();
|
||||
|
||||
for (auto callback : _onSubscribeUserCallbacks) callback(packetId, status);
|
||||
}
|
||||
|
||||
void AsyncMqttClient::_onUnsubAck(uint16_t packetId) {
|
||||
_freeCurrentParsedPacket();
|
||||
|
||||
for (auto callback : _onUnsubscribeUserCallbacks) callback(packetId);
|
||||
}
|
||||
|
||||
void AsyncMqttClient::_onMessage(char* topic, char* payload, uint8_t qos, bool dup, bool retain, size_t len, size_t index, size_t total, uint16_t packetId) {
|
||||
bool notifyPublish = true;
|
||||
|
||||
if (qos == 2) {
|
||||
for (AsyncMqttClientInternals::PendingPubRel pendingPubRel : _pendingPubRels) {
|
||||
if (pendingPubRel.packetId == packetId) {
|
||||
notifyPublish = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (notifyPublish) {
|
||||
AsyncMqttClientMessageProperties properties;
|
||||
properties.qos = qos;
|
||||
properties.dup = dup;
|
||||
properties.retain = retain;
|
||||
|
||||
for (auto callback : _onMessageUserCallbacks) callback(topic, payload, properties, len, index, total);
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncMqttClient::_onPublish(uint16_t packetId, uint8_t qos) {
|
||||
AsyncMqttClientInternals::PendingAck pendingAck;
|
||||
|
||||
if (qos == 1) {
|
||||
pendingAck.packetType = AsyncMqttClientInternals::PacketType.PUBACK;
|
||||
pendingAck.headerFlag = AsyncMqttClientInternals::HeaderFlag.PUBACK_RESERVED;
|
||||
pendingAck.packetId = packetId;
|
||||
_toSendAcks.push_back(pendingAck);
|
||||
} else if (qos == 2) {
|
||||
pendingAck.packetType = AsyncMqttClientInternals::PacketType.PUBREC;
|
||||
pendingAck.headerFlag = AsyncMqttClientInternals::HeaderFlag.PUBREC_RESERVED;
|
||||
pendingAck.packetId = packetId;
|
||||
_toSendAcks.push_back(pendingAck);
|
||||
|
||||
bool pubRelAwaiting = false;
|
||||
for (AsyncMqttClientInternals::PendingPubRel pendingPubRel : _pendingPubRels) {
|
||||
if (pendingPubRel.packetId == packetId) {
|
||||
pubRelAwaiting = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!pubRelAwaiting) {
|
||||
AsyncMqttClientInternals::PendingPubRel pendingPubRel;
|
||||
pendingPubRel.packetId = packetId;
|
||||
_pendingPubRels.push_back(pendingPubRel);
|
||||
}
|
||||
|
||||
_sendAcks();
|
||||
}
|
||||
|
||||
_freeCurrentParsedPacket();
|
||||
}
|
||||
|
||||
void AsyncMqttClient::_onPubRel(uint16_t packetId) {
|
||||
_freeCurrentParsedPacket();
|
||||
|
||||
AsyncMqttClientInternals::PendingAck pendingAck;
|
||||
pendingAck.packetType = AsyncMqttClientInternals::PacketType.PUBCOMP;
|
||||
pendingAck.headerFlag = AsyncMqttClientInternals::HeaderFlag.PUBCOMP_RESERVED;
|
||||
pendingAck.packetId = packetId;
|
||||
_toSendAcks.push_back(pendingAck);
|
||||
|
||||
for (size_t i = 0; i < _pendingPubRels.size(); i++) {
|
||||
if (_pendingPubRels[i].packetId == packetId) {
|
||||
_pendingPubRels.erase(_pendingPubRels.begin() + i);
|
||||
_pendingPubRels.shrink_to_fit();
|
||||
}
|
||||
}
|
||||
|
||||
_sendAcks();
|
||||
}
|
||||
|
||||
void AsyncMqttClient::_onPubAck(uint16_t packetId) {
|
||||
_freeCurrentParsedPacket();
|
||||
|
||||
for (auto callback : _onPublishUserCallbacks) callback(packetId);
|
||||
}
|
||||
|
||||
void AsyncMqttClient::_onPubRec(uint16_t packetId) {
|
||||
_freeCurrentParsedPacket();
|
||||
|
||||
AsyncMqttClientInternals::PendingAck pendingAck;
|
||||
pendingAck.packetType = AsyncMqttClientInternals::PacketType.PUBREL;
|
||||
pendingAck.headerFlag = AsyncMqttClientInternals::HeaderFlag.PUBREL_RESERVED;
|
||||
pendingAck.packetId = packetId;
|
||||
_toSendAcks.push_back(pendingAck);
|
||||
|
||||
_sendAcks();
|
||||
}
|
||||
|
||||
void AsyncMqttClient::_onPubComp(uint16_t packetId) {
|
||||
_freeCurrentParsedPacket();
|
||||
|
||||
for (auto callback : _onPublishUserCallbacks) callback(packetId);
|
||||
}
|
||||
|
||||
bool AsyncMqttClient::_sendPing() {
|
||||
char fixedHeader[2];
|
||||
fixedHeader[0] = AsyncMqttClientInternals::PacketType.PINGREQ;
|
||||
fixedHeader[0] = fixedHeader[0] << 4;
|
||||
fixedHeader[0] = fixedHeader[0] | AsyncMqttClientInternals::HeaderFlag.PINGREQ_RESERVED;
|
||||
fixedHeader[1] = 0;
|
||||
|
||||
size_t neededSpace = 2;
|
||||
|
||||
SEMAPHORE_TAKE(false);
|
||||
if (_client.space() < neededSpace) { SEMAPHORE_GIVE(); return false; }
|
||||
|
||||
_client.add(fixedHeader, 2);
|
||||
_client.send();
|
||||
_lastClientActivity = millis();
|
||||
_lastPingRequestTime = millis();
|
||||
|
||||
SEMAPHORE_GIVE();
|
||||
return true;
|
||||
}
|
||||
|
||||
void AsyncMqttClient::_sendAcks() {
|
||||
uint8_t neededAckSpace = 2 + 2;
|
||||
|
||||
SEMAPHORE_TAKE();
|
||||
for (size_t i = 0; i < _toSendAcks.size(); i++) {
|
||||
if (_client.space() < neededAckSpace) break;
|
||||
|
||||
AsyncMqttClientInternals::PendingAck pendingAck = _toSendAcks[i];
|
||||
|
||||
char fixedHeader[2];
|
||||
fixedHeader[0] = pendingAck.packetType;
|
||||
fixedHeader[0] = fixedHeader[0] << 4;
|
||||
fixedHeader[0] = fixedHeader[0] | pendingAck.headerFlag;
|
||||
fixedHeader[1] = 2;
|
||||
|
||||
char packetIdBytes[2];
|
||||
packetIdBytes[0] = pendingAck.packetId >> 8;
|
||||
packetIdBytes[1] = pendingAck.packetId & 0xFF;
|
||||
|
||||
_client.add(fixedHeader, 2);
|
||||
_client.add(packetIdBytes, 2);
|
||||
_client.send();
|
||||
|
||||
_toSendAcks.erase(_toSendAcks.begin() + i);
|
||||
_toSendAcks.shrink_to_fit();
|
||||
|
||||
_lastClientActivity = millis();
|
||||
}
|
||||
SEMAPHORE_GIVE();
|
||||
}
|
||||
|
||||
bool AsyncMqttClient::_sendDisconnect() {
|
||||
if (!_connected) return true;
|
||||
|
||||
const uint8_t neededSpace = 2;
|
||||
|
||||
SEMAPHORE_TAKE(false);
|
||||
|
||||
if (_client.space() < neededSpace) { SEMAPHORE_GIVE(); return false; }
|
||||
|
||||
char fixedHeader[2];
|
||||
fixedHeader[0] = AsyncMqttClientInternals::PacketType.DISCONNECT;
|
||||
fixedHeader[0] = fixedHeader[0] << 4;
|
||||
fixedHeader[0] = fixedHeader[0] | AsyncMqttClientInternals::HeaderFlag.DISCONNECT_RESERVED;
|
||||
fixedHeader[1] = 0;
|
||||
|
||||
_client.add(fixedHeader, 2);
|
||||
_client.send();
|
||||
_client.close(true);
|
||||
|
||||
_disconnectFlagged = false;
|
||||
|
||||
SEMAPHORE_GIVE();
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t AsyncMqttClient::_getNextPacketId() {
|
||||
uint16_t nextPacketId = _nextPacketId;
|
||||
|
||||
if (_nextPacketId == 65535) _nextPacketId = 0; // 0 is forbidden
|
||||
_nextPacketId++;
|
||||
|
||||
return nextPacketId;
|
||||
}
|
||||
|
||||
bool AsyncMqttClient::connected() const {
|
||||
return _connected;
|
||||
}
|
||||
|
||||
void AsyncMqttClient::connect() {
|
||||
if (_connected) return;
|
||||
|
||||
#if ASYNC_TCP_SSL_ENABLED
|
||||
if (_useIp) {
|
||||
_client.connect(_ip, _port, _secure);
|
||||
} else {
|
||||
_client.connect(_host, _port, _secure);
|
||||
}
|
||||
#else
|
||||
if (_useIp) {
|
||||
_client.connect(_ip, _port);
|
||||
} else {
|
||||
_client.connect(_host, _port);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void AsyncMqttClient::disconnect(bool force) {
|
||||
if (!_connected) return;
|
||||
|
||||
if (force) {
|
||||
_client.close(true);
|
||||
} else {
|
||||
_disconnectFlagged = true;
|
||||
_sendDisconnect();
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t AsyncMqttClient::subscribe(const char* topic, uint8_t qos) {
|
||||
if (!_connected) return 0;
|
||||
|
||||
char fixedHeader[5];
|
||||
fixedHeader[0] = AsyncMqttClientInternals::PacketType.SUBSCRIBE;
|
||||
fixedHeader[0] = fixedHeader[0] << 4;
|
||||
fixedHeader[0] = fixedHeader[0] | AsyncMqttClientInternals::HeaderFlag.SUBSCRIBE_RESERVED;
|
||||
|
||||
uint16_t topicLength = strlen(topic);
|
||||
char topicLengthBytes[2];
|
||||
topicLengthBytes[0] = topicLength >> 8;
|
||||
topicLengthBytes[1] = topicLength & 0xFF;
|
||||
|
||||
char qosByte[1];
|
||||
qosByte[0] = qos;
|
||||
|
||||
uint8_t remainingLengthLength = AsyncMqttClientInternals::Helpers::encodeRemainingLength(2 + 2 + topicLength + 1, fixedHeader + 1);
|
||||
|
||||
size_t neededSpace = 0;
|
||||
neededSpace += 1 + remainingLengthLength;
|
||||
neededSpace += 2;
|
||||
neededSpace += 2;
|
||||
neededSpace += topicLength;
|
||||
neededSpace += 1;
|
||||
|
||||
SEMAPHORE_TAKE(0);
|
||||
if (_client.space() < neededSpace) { SEMAPHORE_GIVE(); return 0; }
|
||||
|
||||
uint16_t packetId = _getNextPacketId();
|
||||
char packetIdBytes[2];
|
||||
packetIdBytes[0] = packetId >> 8;
|
||||
packetIdBytes[1] = packetId & 0xFF;
|
||||
|
||||
_client.add(fixedHeader, 1 + remainingLengthLength);
|
||||
_client.add(packetIdBytes, 2);
|
||||
_client.add(topicLengthBytes, 2);
|
||||
_client.add(topic, topicLength);
|
||||
_client.add(qosByte, 1);
|
||||
_client.send();
|
||||
_lastClientActivity = millis();
|
||||
|
||||
SEMAPHORE_GIVE();
|
||||
return packetId;
|
||||
}
|
||||
|
||||
uint16_t AsyncMqttClient::unsubscribe(const char* topic) {
|
||||
if (!_connected) return 0;
|
||||
|
||||
char fixedHeader[5];
|
||||
fixedHeader[0] = AsyncMqttClientInternals::PacketType.UNSUBSCRIBE;
|
||||
fixedHeader[0] = fixedHeader[0] << 4;
|
||||
fixedHeader[0] = fixedHeader[0] | AsyncMqttClientInternals::HeaderFlag.UNSUBSCRIBE_RESERVED;
|
||||
|
||||
uint16_t topicLength = strlen(topic);
|
||||
char topicLengthBytes[2];
|
||||
topicLengthBytes[0] = topicLength >> 8;
|
||||
topicLengthBytes[1] = topicLength & 0xFF;
|
||||
|
||||
uint8_t remainingLengthLength = AsyncMqttClientInternals::Helpers::encodeRemainingLength(2 + 2 + topicLength, fixedHeader + 1);
|
||||
|
||||
size_t neededSpace = 0;
|
||||
neededSpace += 1 + remainingLengthLength;
|
||||
neededSpace += 2;
|
||||
neededSpace += 2;
|
||||
neededSpace += topicLength;
|
||||
|
||||
SEMAPHORE_TAKE(0);
|
||||
if (_client.space() < neededSpace) { SEMAPHORE_GIVE(); return 0; }
|
||||
|
||||
uint16_t packetId = _getNextPacketId();
|
||||
char packetIdBytes[2];
|
||||
packetIdBytes[0] = packetId >> 8;
|
||||
packetIdBytes[1] = packetId & 0xFF;
|
||||
|
||||
_client.add(fixedHeader, 1 + remainingLengthLength);
|
||||
_client.add(packetIdBytes, 2);
|
||||
_client.add(topicLengthBytes, 2);
|
||||
_client.add(topic, topicLength);
|
||||
_client.send();
|
||||
_lastClientActivity = millis();
|
||||
|
||||
SEMAPHORE_GIVE();
|
||||
return packetId;
|
||||
}
|
||||
|
||||
uint16_t AsyncMqttClient::publish(const char* topic, uint8_t qos, bool retain, const char* payload, size_t length, bool dup, uint16_t message_id) {
|
||||
if (!_connected) return 0;
|
||||
|
||||
char fixedHeader[5];
|
||||
fixedHeader[0] = AsyncMqttClientInternals::PacketType.PUBLISH;
|
||||
fixedHeader[0] = fixedHeader[0] << 4;
|
||||
if (dup) fixedHeader[0] |= AsyncMqttClientInternals::HeaderFlag.PUBLISH_DUP;
|
||||
if (retain) fixedHeader[0] |= AsyncMqttClientInternals::HeaderFlag.PUBLISH_RETAIN;
|
||||
switch (qos) {
|
||||
case 0:
|
||||
fixedHeader[0] |= AsyncMqttClientInternals::HeaderFlag.PUBLISH_QOS0;
|
||||
break;
|
||||
case 1:
|
||||
fixedHeader[0] |= AsyncMqttClientInternals::HeaderFlag.PUBLISH_QOS1;
|
||||
break;
|
||||
case 2:
|
||||
fixedHeader[0] |= AsyncMqttClientInternals::HeaderFlag.PUBLISH_QOS2;
|
||||
break;
|
||||
}
|
||||
|
||||
uint16_t topicLength = strlen(topic);
|
||||
char topicLengthBytes[2];
|
||||
topicLengthBytes[0] = topicLength >> 8;
|
||||
topicLengthBytes[1] = topicLength & 0xFF;
|
||||
|
||||
uint32_t payloadLength = length;
|
||||
if (payload != nullptr && payloadLength == 0) payloadLength = strlen(payload);
|
||||
|
||||
uint32_t remainingLength = 2 + topicLength + payloadLength;
|
||||
if (qos != 0) remainingLength += 2;
|
||||
uint8_t remainingLengthLength = AsyncMqttClientInternals::Helpers::encodeRemainingLength(remainingLength, fixedHeader + 1);
|
||||
|
||||
size_t neededSpace = 0;
|
||||
neededSpace += 1 + remainingLengthLength;
|
||||
neededSpace += 2;
|
||||
neededSpace += topicLength;
|
||||
if (qos != 0) neededSpace += 2;
|
||||
if (payload != nullptr) neededSpace += payloadLength;
|
||||
|
||||
SEMAPHORE_TAKE(0);
|
||||
if (_client.space() < neededSpace) { SEMAPHORE_GIVE(); return 0; }
|
||||
|
||||
uint16_t packetId = 0;
|
||||
char packetIdBytes[2];
|
||||
if (qos != 0) {
|
||||
if (dup && message_id > 0) {
|
||||
packetId = message_id;
|
||||
} else {
|
||||
packetId = _getNextPacketId();
|
||||
}
|
||||
|
||||
packetIdBytes[0] = packetId >> 8;
|
||||
packetIdBytes[1] = packetId & 0xFF;
|
||||
}
|
||||
|
||||
_client.add(fixedHeader, 1 + remainingLengthLength);
|
||||
_client.add(topicLengthBytes, 2);
|
||||
_client.add(topic, topicLength);
|
||||
if (qos != 0) _client.add(packetIdBytes, 2);
|
||||
if (payload != nullptr) _client.add(payload, payloadLength);
|
||||
_client.send();
|
||||
_lastClientActivity = millis();
|
||||
|
||||
SEMAPHORE_GIVE();
|
||||
if (qos != 0) {
|
||||
return packetId;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef SRC_ASYNCMQTTCLIENT_H_
|
||||
#define SRC_ASYNCMQTTCLIENT_H_
|
||||
|
||||
#include "AsyncMqttClient.hpp"
|
||||
|
||||
#endif // SRC_ASYNCMQTTCLIENT_H_
|
||||
166
wled00/src/dependencies/async-mqtt-client/AsyncMqttClient.hpp
Normal file
166
wled00/src/dependencies/async-mqtt-client/AsyncMqttClient.hpp
Normal file
@@ -0,0 +1,166 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
#ifdef ESP32
|
||||
#include <AsyncTCP.h>
|
||||
#include <freertos/semphr.h>
|
||||
#elif defined(ESP8266)
|
||||
#include <ESPAsyncTCP.h>
|
||||
#else
|
||||
#error Platform not supported
|
||||
#endif
|
||||
|
||||
#if ASYNC_TCP_SSL_ENABLED
|
||||
#include <tcp_axtls.h>
|
||||
#define SHA1_SIZE 20
|
||||
#endif
|
||||
|
||||
#include "AsyncMqttClient/Flags.hpp"
|
||||
#include "AsyncMqttClient/ParsingInformation.hpp"
|
||||
#include "AsyncMqttClient/MessageProperties.hpp"
|
||||
#include "AsyncMqttClient/Helpers.hpp"
|
||||
#include "AsyncMqttClient/Callbacks.hpp"
|
||||
#include "AsyncMqttClient/DisconnectReasons.hpp"
|
||||
#include "AsyncMqttClient/Storage.hpp"
|
||||
|
||||
#include "AsyncMqttClient/Packets/Packet.hpp"
|
||||
#include "AsyncMqttClient/Packets/ConnAckPacket.hpp"
|
||||
#include "AsyncMqttClient/Packets/PingRespPacket.hpp"
|
||||
#include "AsyncMqttClient/Packets/SubAckPacket.hpp"
|
||||
#include "AsyncMqttClient/Packets/UnsubAckPacket.hpp"
|
||||
#include "AsyncMqttClient/Packets/PublishPacket.hpp"
|
||||
#include "AsyncMqttClient/Packets/PubRelPacket.hpp"
|
||||
#include "AsyncMqttClient/Packets/PubAckPacket.hpp"
|
||||
#include "AsyncMqttClient/Packets/PubRecPacket.hpp"
|
||||
#include "AsyncMqttClient/Packets/PubCompPacket.hpp"
|
||||
|
||||
#if ESP32
|
||||
#define SEMAPHORE_TAKE(X) if (xSemaphoreTake(_xSemaphore, 1000 / portTICK_PERIOD_MS) != pdTRUE) { return X; } // Waits max 1000ms
|
||||
#define SEMAPHORE_GIVE() xSemaphoreGive(_xSemaphore);
|
||||
#elif defined(ESP8266)
|
||||
#define SEMAPHORE_TAKE(X) void()
|
||||
#define SEMAPHORE_GIVE() void()
|
||||
#endif
|
||||
|
||||
class AsyncMqttClient {
|
||||
public:
|
||||
AsyncMqttClient();
|
||||
~AsyncMqttClient();
|
||||
|
||||
AsyncMqttClient& setKeepAlive(uint16_t keepAlive);
|
||||
AsyncMqttClient& setClientId(const char* clientId);
|
||||
AsyncMqttClient& setCleanSession(bool cleanSession);
|
||||
AsyncMqttClient& setMaxTopicLength(uint16_t maxTopicLength);
|
||||
AsyncMqttClient& setCredentials(const char* username, const char* password = nullptr);
|
||||
AsyncMqttClient& setWill(const char* topic, uint8_t qos, bool retain, const char* payload = nullptr, size_t length = 0);
|
||||
AsyncMqttClient& setServer(IPAddress ip, uint16_t port);
|
||||
AsyncMqttClient& setServer(const char* host, uint16_t port);
|
||||
#if ASYNC_TCP_SSL_ENABLED
|
||||
AsyncMqttClient& setSecure(bool secure);
|
||||
AsyncMqttClient& addServerFingerprint(const uint8_t* fingerprint);
|
||||
#endif
|
||||
|
||||
AsyncMqttClient& onConnect(AsyncMqttClientInternals::OnConnectUserCallback callback);
|
||||
AsyncMqttClient& onDisconnect(AsyncMqttClientInternals::OnDisconnectUserCallback callback);
|
||||
AsyncMqttClient& onSubscribe(AsyncMqttClientInternals::OnSubscribeUserCallback callback);
|
||||
AsyncMqttClient& onUnsubscribe(AsyncMqttClientInternals::OnUnsubscribeUserCallback callback);
|
||||
AsyncMqttClient& onMessage(AsyncMqttClientInternals::OnMessageUserCallback callback);
|
||||
AsyncMqttClient& onPublish(AsyncMqttClientInternals::OnPublishUserCallback callback);
|
||||
|
||||
bool connected() const;
|
||||
void connect();
|
||||
void disconnect(bool force = false);
|
||||
uint16_t subscribe(const char* topic, uint8_t qos);
|
||||
uint16_t unsubscribe(const char* topic);
|
||||
uint16_t publish(const char* topic, uint8_t qos, bool retain, const char* payload = nullptr, size_t length = 0, bool dup = false, uint16_t message_id = 0);
|
||||
|
||||
private:
|
||||
AsyncClient _client;
|
||||
|
||||
bool _connected;
|
||||
bool _connectPacketNotEnoughSpace;
|
||||
bool _disconnectFlagged;
|
||||
bool _tlsBadFingerprint;
|
||||
uint32_t _lastClientActivity;
|
||||
uint32_t _lastServerActivity;
|
||||
uint32_t _lastPingRequestTime;
|
||||
|
||||
char _generatedClientId[13 + 1]; // esp8266abc123
|
||||
IPAddress _ip;
|
||||
const char* _host;
|
||||
bool _useIp;
|
||||
#if ASYNC_TCP_SSL_ENABLED
|
||||
bool _secure;
|
||||
#endif
|
||||
uint16_t _port;
|
||||
uint16_t _keepAlive;
|
||||
bool _cleanSession;
|
||||
const char* _clientId;
|
||||
const char* _username;
|
||||
const char* _password;
|
||||
const char* _willTopic;
|
||||
const char* _willPayload;
|
||||
uint16_t _willPayloadLength;
|
||||
uint8_t _willQos;
|
||||
bool _willRetain;
|
||||
|
||||
#if ASYNC_TCP_SSL_ENABLED
|
||||
std::vector<std::array<uint8_t, SHA1_SIZE>> _secureServerFingerprints;
|
||||
#endif
|
||||
|
||||
std::vector<AsyncMqttClientInternals::OnConnectUserCallback> _onConnectUserCallbacks;
|
||||
std::vector<AsyncMqttClientInternals::OnDisconnectUserCallback> _onDisconnectUserCallbacks;
|
||||
std::vector<AsyncMqttClientInternals::OnSubscribeUserCallback> _onSubscribeUserCallbacks;
|
||||
std::vector<AsyncMqttClientInternals::OnUnsubscribeUserCallback> _onUnsubscribeUserCallbacks;
|
||||
std::vector<AsyncMqttClientInternals::OnMessageUserCallback> _onMessageUserCallbacks;
|
||||
std::vector<AsyncMqttClientInternals::OnPublishUserCallback> _onPublishUserCallbacks;
|
||||
|
||||
AsyncMqttClientInternals::ParsingInformation _parsingInformation;
|
||||
AsyncMqttClientInternals::Packet* _currentParsedPacket;
|
||||
uint8_t _remainingLengthBufferPosition;
|
||||
char _remainingLengthBuffer[4];
|
||||
|
||||
uint16_t _nextPacketId;
|
||||
|
||||
std::vector<AsyncMqttClientInternals::PendingPubRel> _pendingPubRels;
|
||||
|
||||
std::vector<AsyncMqttClientInternals::PendingAck> _toSendAcks;
|
||||
|
||||
#ifdef ESP32
|
||||
SemaphoreHandle_t _xSemaphore = nullptr;
|
||||
#endif
|
||||
|
||||
void _clear();
|
||||
void _freeCurrentParsedPacket();
|
||||
|
||||
// TCP
|
||||
void _onConnect(AsyncClient* client);
|
||||
void _onDisconnect(AsyncClient* client);
|
||||
static void _onError(AsyncClient* client, int8_t error);
|
||||
void _onTimeout(AsyncClient* client, uint32_t time);
|
||||
static void _onAck(AsyncClient* client, size_t len, uint32_t time);
|
||||
void _onData(AsyncClient* client, char* data, size_t len);
|
||||
void _onPoll(AsyncClient* client);
|
||||
|
||||
// MQTT
|
||||
void _onPingResp();
|
||||
void _onConnAck(bool sessionPresent, uint8_t connectReturnCode);
|
||||
void _onSubAck(uint16_t packetId, char status);
|
||||
void _onUnsubAck(uint16_t packetId);
|
||||
void _onMessage(char* topic, char* payload, uint8_t qos, bool dup, bool retain, size_t len, size_t index, size_t total, uint16_t packetId);
|
||||
void _onPublish(uint16_t packetId, uint8_t qos);
|
||||
void _onPubRel(uint16_t packetId);
|
||||
void _onPubAck(uint16_t packetId);
|
||||
void _onPubRec(uint16_t packetId);
|
||||
void _onPubComp(uint16_t packetId);
|
||||
|
||||
bool _sendPing();
|
||||
void _sendAcks();
|
||||
bool _sendDisconnect();
|
||||
|
||||
uint16_t _getNextPacketId();
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "DisconnectReasons.hpp"
|
||||
#include "MessageProperties.hpp"
|
||||
|
||||
namespace AsyncMqttClientInternals {
|
||||
// user callbacks
|
||||
typedef std::function<void(bool sessionPresent)> OnConnectUserCallback;
|
||||
typedef std::function<void(AsyncMqttClientDisconnectReason reason)> OnDisconnectUserCallback;
|
||||
typedef std::function<void(uint16_t packetId, uint8_t qos)> OnSubscribeUserCallback;
|
||||
typedef std::function<void(uint16_t packetId)> OnUnsubscribeUserCallback;
|
||||
typedef std::function<void(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total)> OnMessageUserCallback;
|
||||
typedef std::function<void(uint16_t packetId)> OnPublishUserCallback;
|
||||
|
||||
// internal callbacks
|
||||
typedef std::function<void(bool sessionPresent, uint8_t connectReturnCode)> OnConnAckInternalCallback;
|
||||
typedef std::function<void()> OnPingRespInternalCallback;
|
||||
typedef std::function<void(uint16_t packetId, char status)> OnSubAckInternalCallback;
|
||||
typedef std::function<void(uint16_t packetId)> OnUnsubAckInternalCallback;
|
||||
typedef std::function<void(char* topic, char* payload, uint8_t qos, bool dup, bool retain, size_t len, size_t index, size_t total, uint16_t packetId)> OnMessageInternalCallback;
|
||||
typedef std::function<void(uint16_t packetId, uint8_t qos)> OnPublishInternalCallback;
|
||||
typedef std::function<void(uint16_t packetId)> OnPubRelInternalCallback;
|
||||
typedef std::function<void(uint16_t packetId)> OnPubAckInternalCallback;
|
||||
typedef std::function<void(uint16_t packetId)> OnPubRecInternalCallback;
|
||||
typedef std::function<void(uint16_t packetId)> OnPubCompInternalCallback;
|
||||
} // namespace AsyncMqttClientInternals
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
enum class AsyncMqttClientDisconnectReason : int8_t {
|
||||
TCP_DISCONNECTED = 0,
|
||||
|
||||
MQTT_UNACCEPTABLE_PROTOCOL_VERSION = 1,
|
||||
MQTT_IDENTIFIER_REJECTED = 2,
|
||||
MQTT_SERVER_UNAVAILABLE = 3,
|
||||
MQTT_MALFORMED_CREDENTIALS = 4,
|
||||
MQTT_NOT_AUTHORIZED = 5,
|
||||
|
||||
ESP8266_NOT_ENOUGH_SPACE = 6,
|
||||
|
||||
TLS_BAD_FINGERPRINT = 7
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
namespace AsyncMqttClientInternals {
|
||||
constexpr struct {
|
||||
const uint8_t RESERVED = 0;
|
||||
const uint8_t CONNECT = 1;
|
||||
const uint8_t CONNACK = 2;
|
||||
const uint8_t PUBLISH = 3;
|
||||
const uint8_t PUBACK = 4;
|
||||
const uint8_t PUBREC = 5;
|
||||
const uint8_t PUBREL = 6;
|
||||
const uint8_t PUBCOMP = 7;
|
||||
const uint8_t SUBSCRIBE = 8;
|
||||
const uint8_t SUBACK = 9;
|
||||
const uint8_t UNSUBSCRIBE = 10;
|
||||
const uint8_t UNSUBACK = 11;
|
||||
const uint8_t PINGREQ = 12;
|
||||
const uint8_t PINGRESP = 13;
|
||||
const uint8_t DISCONNECT = 14;
|
||||
const uint8_t RESERVED2 = 1;
|
||||
} PacketType;
|
||||
|
||||
constexpr struct {
|
||||
const uint8_t CONNECT_RESERVED = 0x00;
|
||||
const uint8_t CONNACK_RESERVED = 0x00;
|
||||
const uint8_t PUBLISH_DUP = 0x08;
|
||||
const uint8_t PUBLISH_QOS0 = 0x00;
|
||||
const uint8_t PUBLISH_QOS1 = 0x02;
|
||||
const uint8_t PUBLISH_QOS2 = 0x04;
|
||||
const uint8_t PUBLISH_QOSRESERVED = 0x06;
|
||||
const uint8_t PUBLISH_RETAIN = 0x01;
|
||||
const uint8_t PUBACK_RESERVED = 0x00;
|
||||
const uint8_t PUBREC_RESERVED = 0x00;
|
||||
const uint8_t PUBREL_RESERVED = 0x02;
|
||||
const uint8_t PUBCOMP_RESERVED = 0x00;
|
||||
const uint8_t SUBSCRIBE_RESERVED = 0x02;
|
||||
const uint8_t SUBACK_RESERVED = 0x00;
|
||||
const uint8_t UNSUBSCRIBE_RESERVED = 0x02;
|
||||
const uint8_t UNSUBACK_RESERVED = 0x00;
|
||||
const uint8_t PINGREQ_RESERVED = 0x00;
|
||||
const uint8_t PINGRESP_RESERVED = 0x00;
|
||||
const uint8_t DISCONNECT_RESERVED = 0x00;
|
||||
const uint8_t RESERVED2_RESERVED = 0x00;
|
||||
} HeaderFlag;
|
||||
|
||||
constexpr struct {
|
||||
const uint8_t USERNAME = 0x80;
|
||||
const uint8_t PASSWORD = 0x40;
|
||||
const uint8_t WILL_RETAIN = 0x20;
|
||||
const uint8_t WILL_QOS0 = 0x00;
|
||||
const uint8_t WILL_QOS1 = 0x08;
|
||||
const uint8_t WILL_QOS2 = 0x10;
|
||||
const uint8_t WILL = 0x04;
|
||||
const uint8_t CLEAN_SESSION = 0x02;
|
||||
const uint8_t RESERVED = 0x00;
|
||||
} ConnectFlag;
|
||||
} // namespace AsyncMqttClientInternals
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
namespace AsyncMqttClientInternals {
|
||||
class Helpers {
|
||||
public:
|
||||
static uint32_t decodeRemainingLength(char* bytes) {
|
||||
uint32_t multiplier = 1;
|
||||
uint32_t value = 0;
|
||||
uint8_t currentByte = 0;
|
||||
uint8_t encodedByte;
|
||||
do {
|
||||
encodedByte = bytes[currentByte++];
|
||||
value += (encodedByte & 127) * multiplier;
|
||||
multiplier *= 128;
|
||||
} while ((encodedByte & 128) != 0);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static uint8_t encodeRemainingLength(uint32_t remainingLength, char* destination) {
|
||||
uint8_t currentByte = 0;
|
||||
uint8_t bytesNeeded = 0;
|
||||
|
||||
do {
|
||||
uint8_t encodedByte = remainingLength % 128;
|
||||
remainingLength /= 128;
|
||||
if (remainingLength > 0) {
|
||||
encodedByte = encodedByte | 128;
|
||||
}
|
||||
|
||||
destination[currentByte++] = encodedByte;
|
||||
bytesNeeded++;
|
||||
} while (remainingLength > 0);
|
||||
|
||||
return bytesNeeded;
|
||||
}
|
||||
};
|
||||
} // namespace AsyncMqttClientInternals
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
struct AsyncMqttClientMessageProperties {
|
||||
uint8_t qos;
|
||||
bool dup;
|
||||
bool retain;
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
#include "ConnAckPacket.hpp"
|
||||
|
||||
using AsyncMqttClientInternals::ConnAckPacket;
|
||||
|
||||
ConnAckPacket::ConnAckPacket(ParsingInformation* parsingInformation, OnConnAckInternalCallback callback)
|
||||
: _parsingInformation(parsingInformation)
|
||||
, _callback(callback)
|
||||
, _bytePosition(0)
|
||||
, _sessionPresent(false)
|
||||
, _connectReturnCode(0) {
|
||||
}
|
||||
|
||||
ConnAckPacket::~ConnAckPacket() {
|
||||
}
|
||||
|
||||
void ConnAckPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) {
|
||||
char currentByte = data[(*currentBytePosition)++];
|
||||
if (_bytePosition++ == 0) {
|
||||
_sessionPresent = (currentByte << 7) >> 7;
|
||||
} else {
|
||||
_connectReturnCode = currentByte;
|
||||
_parsingInformation->bufferState = BufferState::NONE;
|
||||
_callback(_sessionPresent, _connectReturnCode);
|
||||
}
|
||||
}
|
||||
|
||||
void ConnAckPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) {
|
||||
(void)data;
|
||||
(void)currentBytePosition;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "Packet.hpp"
|
||||
#include "../ParsingInformation.hpp"
|
||||
#include "../Callbacks.hpp"
|
||||
|
||||
namespace AsyncMqttClientInternals {
|
||||
class ConnAckPacket : public Packet {
|
||||
public:
|
||||
explicit ConnAckPacket(ParsingInformation* parsingInformation, OnConnAckInternalCallback callback);
|
||||
~ConnAckPacket();
|
||||
|
||||
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition);
|
||||
void parsePayload(char* data, size_t len, size_t* currentBytePosition);
|
||||
|
||||
private:
|
||||
ParsingInformation* _parsingInformation;
|
||||
OnConnAckInternalCallback _callback;
|
||||
|
||||
uint8_t _bytePosition;
|
||||
bool _sessionPresent;
|
||||
uint8_t _connectReturnCode;
|
||||
};
|
||||
} // namespace AsyncMqttClientInternals
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
namespace AsyncMqttClientInternals {
|
||||
class Packet {
|
||||
public:
|
||||
virtual ~Packet() {}
|
||||
|
||||
virtual void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) = 0;
|
||||
virtual void parsePayload(char* data, size_t len, size_t* currentBytePosition) = 0;
|
||||
};
|
||||
} // namespace AsyncMqttClientInternals
|
||||
@@ -0,0 +1,21 @@
|
||||
#include "PingRespPacket.hpp"
|
||||
|
||||
using AsyncMqttClientInternals::PingRespPacket;
|
||||
|
||||
PingRespPacket::PingRespPacket(ParsingInformation* parsingInformation, OnPingRespInternalCallback callback)
|
||||
: _parsingInformation(parsingInformation)
|
||||
, _callback(callback) {
|
||||
}
|
||||
|
||||
PingRespPacket::~PingRespPacket() {
|
||||
}
|
||||
|
||||
void PingRespPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) {
|
||||
(void)data;
|
||||
(void)currentBytePosition;
|
||||
}
|
||||
|
||||
void PingRespPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) {
|
||||
(void)data;
|
||||
(void)currentBytePosition;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "Packet.hpp"
|
||||
#include "../ParsingInformation.hpp"
|
||||
#include "../Callbacks.hpp"
|
||||
|
||||
namespace AsyncMqttClientInternals {
|
||||
class PingRespPacket : public Packet {
|
||||
public:
|
||||
explicit PingRespPacket(ParsingInformation* parsingInformation, OnPingRespInternalCallback callback);
|
||||
~PingRespPacket();
|
||||
|
||||
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition);
|
||||
void parsePayload(char* data, size_t len, size_t* currentBytePosition);
|
||||
|
||||
private:
|
||||
ParsingInformation* _parsingInformation;
|
||||
OnPingRespInternalCallback _callback;
|
||||
};
|
||||
} // namespace AsyncMqttClientInternals
|
||||
@@ -0,0 +1,30 @@
|
||||
#include "PubAckPacket.hpp"
|
||||
|
||||
using AsyncMqttClientInternals::PubAckPacket;
|
||||
|
||||
PubAckPacket::PubAckPacket(ParsingInformation* parsingInformation, OnPubAckInternalCallback callback)
|
||||
: _parsingInformation(parsingInformation)
|
||||
, _callback(callback)
|
||||
, _bytePosition(0)
|
||||
, _packetIdMsb(0)
|
||||
, _packetId(0) {
|
||||
}
|
||||
|
||||
PubAckPacket::~PubAckPacket() {
|
||||
}
|
||||
|
||||
void PubAckPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) {
|
||||
char currentByte = data[(*currentBytePosition)++];
|
||||
if (_bytePosition++ == 0) {
|
||||
_packetIdMsb = currentByte;
|
||||
} else {
|
||||
_packetId = currentByte | _packetIdMsb << 8;
|
||||
_parsingInformation->bufferState = BufferState::NONE;
|
||||
_callback(_packetId);
|
||||
}
|
||||
}
|
||||
|
||||
void PubAckPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) {
|
||||
(void)data;
|
||||
(void)currentBytePosition;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "Packet.hpp"
|
||||
#include "../ParsingInformation.hpp"
|
||||
#include "../Callbacks.hpp"
|
||||
|
||||
namespace AsyncMqttClientInternals {
|
||||
class PubAckPacket : public Packet {
|
||||
public:
|
||||
explicit PubAckPacket(ParsingInformation* parsingInformation, OnPubAckInternalCallback callback);
|
||||
~PubAckPacket();
|
||||
|
||||
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition);
|
||||
void parsePayload(char* data, size_t len, size_t* currentBytePosition);
|
||||
|
||||
private:
|
||||
ParsingInformation* _parsingInformation;
|
||||
OnPubAckInternalCallback _callback;
|
||||
|
||||
uint8_t _bytePosition;
|
||||
char _packetIdMsb;
|
||||
uint16_t _packetId;
|
||||
};
|
||||
} // namespace AsyncMqttClientInternals
|
||||
@@ -0,0 +1,30 @@
|
||||
#include "PubCompPacket.hpp"
|
||||
|
||||
using AsyncMqttClientInternals::PubCompPacket;
|
||||
|
||||
PubCompPacket::PubCompPacket(ParsingInformation* parsingInformation, OnPubCompInternalCallback callback)
|
||||
: _parsingInformation(parsingInformation)
|
||||
, _callback(callback)
|
||||
, _bytePosition(0)
|
||||
, _packetIdMsb(0)
|
||||
, _packetId(0) {
|
||||
}
|
||||
|
||||
PubCompPacket::~PubCompPacket() {
|
||||
}
|
||||
|
||||
void PubCompPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) {
|
||||
char currentByte = data[(*currentBytePosition)++];
|
||||
if (_bytePosition++ == 0) {
|
||||
_packetIdMsb = currentByte;
|
||||
} else {
|
||||
_packetId = currentByte | _packetIdMsb << 8;
|
||||
_parsingInformation->bufferState = BufferState::NONE;
|
||||
_callback(_packetId);
|
||||
}
|
||||
}
|
||||
|
||||
void PubCompPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) {
|
||||
(void)data;
|
||||
(void)currentBytePosition;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "Packet.hpp"
|
||||
#include "../ParsingInformation.hpp"
|
||||
#include "../Callbacks.hpp"
|
||||
|
||||
namespace AsyncMqttClientInternals {
|
||||
class PubCompPacket : public Packet {
|
||||
public:
|
||||
explicit PubCompPacket(ParsingInformation* parsingInformation, OnPubCompInternalCallback callback);
|
||||
~PubCompPacket();
|
||||
|
||||
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition);
|
||||
void parsePayload(char* data, size_t len, size_t* currentBytePosition);
|
||||
|
||||
private:
|
||||
ParsingInformation* _parsingInformation;
|
||||
OnPubCompInternalCallback _callback;
|
||||
|
||||
uint8_t _bytePosition;
|
||||
char _packetIdMsb;
|
||||
uint16_t _packetId;
|
||||
};
|
||||
} // namespace AsyncMqttClientInternals
|
||||
@@ -0,0 +1,30 @@
|
||||
#include "PubRecPacket.hpp"
|
||||
|
||||
using AsyncMqttClientInternals::PubRecPacket;
|
||||
|
||||
PubRecPacket::PubRecPacket(ParsingInformation* parsingInformation, OnPubRecInternalCallback callback)
|
||||
: _parsingInformation(parsingInformation)
|
||||
, _callback(callback)
|
||||
, _bytePosition(0)
|
||||
, _packetIdMsb(0)
|
||||
, _packetId(0) {
|
||||
}
|
||||
|
||||
PubRecPacket::~PubRecPacket() {
|
||||
}
|
||||
|
||||
void PubRecPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) {
|
||||
char currentByte = data[(*currentBytePosition)++];
|
||||
if (_bytePosition++ == 0) {
|
||||
_packetIdMsb = currentByte;
|
||||
} else {
|
||||
_packetId = currentByte | _packetIdMsb << 8;
|
||||
_parsingInformation->bufferState = BufferState::NONE;
|
||||
_callback(_packetId);
|
||||
}
|
||||
}
|
||||
|
||||
void PubRecPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) {
|
||||
(void)data;
|
||||
(void)currentBytePosition;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "Packet.hpp"
|
||||
#include "../ParsingInformation.hpp"
|
||||
#include "../Callbacks.hpp"
|
||||
|
||||
namespace AsyncMqttClientInternals {
|
||||
class PubRecPacket : public Packet {
|
||||
public:
|
||||
explicit PubRecPacket(ParsingInformation* parsingInformation, OnPubRecInternalCallback callback);
|
||||
~PubRecPacket();
|
||||
|
||||
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition);
|
||||
void parsePayload(char* data, size_t len, size_t* currentBytePosition);
|
||||
|
||||
private:
|
||||
ParsingInformation* _parsingInformation;
|
||||
OnPubRecInternalCallback _callback;
|
||||
|
||||
uint8_t _bytePosition;
|
||||
char _packetIdMsb;
|
||||
uint16_t _packetId;
|
||||
};
|
||||
} // namespace AsyncMqttClientInternals
|
||||
@@ -0,0 +1,30 @@
|
||||
#include "PubRelPacket.hpp"
|
||||
|
||||
using AsyncMqttClientInternals::PubRelPacket;
|
||||
|
||||
PubRelPacket::PubRelPacket(ParsingInformation* parsingInformation, OnPubRelInternalCallback callback)
|
||||
: _parsingInformation(parsingInformation)
|
||||
, _callback(callback)
|
||||
, _bytePosition(0)
|
||||
, _packetIdMsb(0)
|
||||
, _packetId(0) {
|
||||
}
|
||||
|
||||
PubRelPacket::~PubRelPacket() {
|
||||
}
|
||||
|
||||
void PubRelPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) {
|
||||
char currentByte = data[(*currentBytePosition)++];
|
||||
if (_bytePosition++ == 0) {
|
||||
_packetIdMsb = currentByte;
|
||||
} else {
|
||||
_packetId = currentByte | _packetIdMsb << 8;
|
||||
_parsingInformation->bufferState = BufferState::NONE;
|
||||
_callback(_packetId);
|
||||
}
|
||||
}
|
||||
|
||||
void PubRelPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) {
|
||||
(void)data;
|
||||
(void)currentBytePosition;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "Packet.hpp"
|
||||
#include "../ParsingInformation.hpp"
|
||||
#include "../Callbacks.hpp"
|
||||
|
||||
namespace AsyncMqttClientInternals {
|
||||
class PubRelPacket : public Packet {
|
||||
public:
|
||||
explicit PubRelPacket(ParsingInformation* parsingInformation, OnPubRelInternalCallback callback);
|
||||
~PubRelPacket();
|
||||
|
||||
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition);
|
||||
void parsePayload(char* data, size_t len, size_t* currentBytePosition);
|
||||
|
||||
private:
|
||||
ParsingInformation* _parsingInformation;
|
||||
OnPubRelInternalCallback _callback;
|
||||
|
||||
uint8_t _bytePosition;
|
||||
char _packetIdMsb;
|
||||
uint16_t _packetId;
|
||||
};
|
||||
} // namespace AsyncMqttClientInternals
|
||||
@@ -0,0 +1,91 @@
|
||||
#include "PublishPacket.hpp"
|
||||
|
||||
using AsyncMqttClientInternals::PublishPacket;
|
||||
|
||||
PublishPacket::PublishPacket(ParsingInformation* parsingInformation, OnMessageInternalCallback dataCallback, OnPublishInternalCallback completeCallback)
|
||||
: _parsingInformation(parsingInformation)
|
||||
, _dataCallback(dataCallback)
|
||||
, _completeCallback(completeCallback)
|
||||
, _dup(false)
|
||||
, _qos(0)
|
||||
, _retain(0)
|
||||
, _bytePosition(0)
|
||||
, _topicLengthMsb(0)
|
||||
, _topicLength(0)
|
||||
, _ignore(false)
|
||||
, _packetIdMsb(0)
|
||||
, _packetId(0)
|
||||
, _payloadLength(0)
|
||||
, _payloadBytesRead(0) {
|
||||
_dup = _parsingInformation->packetFlags & HeaderFlag.PUBLISH_DUP;
|
||||
_retain = _parsingInformation->packetFlags & HeaderFlag.PUBLISH_RETAIN;
|
||||
char qosMasked = _parsingInformation->packetFlags & 0x06;
|
||||
switch (qosMasked) {
|
||||
case HeaderFlag.PUBLISH_QOS0:
|
||||
_qos = 0;
|
||||
break;
|
||||
case HeaderFlag.PUBLISH_QOS1:
|
||||
_qos = 1;
|
||||
break;
|
||||
case HeaderFlag.PUBLISH_QOS2:
|
||||
_qos = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
PublishPacket::~PublishPacket() {
|
||||
}
|
||||
|
||||
void PublishPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) {
|
||||
char currentByte = data[(*currentBytePosition)++];
|
||||
if (_bytePosition == 0) {
|
||||
_topicLengthMsb = currentByte;
|
||||
} else if (_bytePosition == 1) {
|
||||
_topicLength = currentByte | _topicLengthMsb << 8;
|
||||
if (_topicLength > _parsingInformation->maxTopicLength) {
|
||||
_ignore = true;
|
||||
} else {
|
||||
_parsingInformation->topicBuffer[_topicLength] = '\0';
|
||||
}
|
||||
} else if (_bytePosition >= 2 && _bytePosition < 2 + _topicLength) {
|
||||
// Starting from here, _ignore might be true
|
||||
if (!_ignore) _parsingInformation->topicBuffer[_bytePosition - 2] = currentByte;
|
||||
if (_bytePosition == 2 + _topicLength - 1 && _qos == 0) {
|
||||
_preparePayloadHandling(_parsingInformation->remainingLength - (_bytePosition + 1));
|
||||
return;
|
||||
}
|
||||
} else if (_bytePosition == 2 + _topicLength) {
|
||||
_packetIdMsb = currentByte;
|
||||
} else {
|
||||
_packetId = currentByte | _packetIdMsb << 8;
|
||||
_preparePayloadHandling(_parsingInformation->remainingLength - (_bytePosition + 1));
|
||||
}
|
||||
_bytePosition++;
|
||||
}
|
||||
|
||||
void PublishPacket::_preparePayloadHandling(uint32_t payloadLength) {
|
||||
_payloadLength = payloadLength;
|
||||
if (payloadLength == 0) {
|
||||
_parsingInformation->bufferState = BufferState::NONE;
|
||||
if (!_ignore) {
|
||||
_dataCallback(_parsingInformation->topicBuffer, nullptr, _qos, _dup, _retain, 0, 0, 0, _packetId);
|
||||
_completeCallback(_packetId, _qos);
|
||||
}
|
||||
} else {
|
||||
_parsingInformation->bufferState = BufferState::PAYLOAD;
|
||||
}
|
||||
}
|
||||
|
||||
void PublishPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) {
|
||||
size_t remainToRead = len - (*currentBytePosition);
|
||||
if (_payloadBytesRead + remainToRead > _payloadLength) remainToRead = _payloadLength - _payloadBytesRead;
|
||||
|
||||
if (!_ignore) _dataCallback(_parsingInformation->topicBuffer, data + (*currentBytePosition), _qos, _dup, _retain, remainToRead, _payloadBytesRead, _payloadLength, _packetId);
|
||||
_payloadBytesRead += remainToRead;
|
||||
(*currentBytePosition) += remainToRead;
|
||||
|
||||
if (_payloadBytesRead == _payloadLength) {
|
||||
_parsingInformation->bufferState = BufferState::NONE;
|
||||
if (!_ignore) _completeCallback(_packetId, _qos);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "Packet.hpp"
|
||||
#include "../Flags.hpp"
|
||||
#include "../ParsingInformation.hpp"
|
||||
#include "../Callbacks.hpp"
|
||||
|
||||
namespace AsyncMqttClientInternals {
|
||||
class PublishPacket : public Packet {
|
||||
public:
|
||||
explicit PublishPacket(ParsingInformation* parsingInformation, OnMessageInternalCallback dataCallback, OnPublishInternalCallback completeCallback);
|
||||
~PublishPacket();
|
||||
|
||||
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition);
|
||||
void parsePayload(char* data, size_t len, size_t* currentBytePosition);
|
||||
|
||||
private:
|
||||
ParsingInformation* _parsingInformation;
|
||||
OnMessageInternalCallback _dataCallback;
|
||||
OnPublishInternalCallback _completeCallback;
|
||||
|
||||
void _preparePayloadHandling(uint32_t payloadLength);
|
||||
|
||||
bool _dup;
|
||||
uint8_t _qos;
|
||||
bool _retain;
|
||||
|
||||
uint8_t _bytePosition;
|
||||
char _topicLengthMsb;
|
||||
uint16_t _topicLength;
|
||||
bool _ignore;
|
||||
char _packetIdMsb;
|
||||
uint16_t _packetId;
|
||||
uint32_t _payloadLength;
|
||||
uint32_t _payloadBytesRead;
|
||||
};
|
||||
} // namespace AsyncMqttClientInternals
|
||||
@@ -0,0 +1,46 @@
|
||||
#include "SubAckPacket.hpp"
|
||||
|
||||
using AsyncMqttClientInternals::SubAckPacket;
|
||||
|
||||
SubAckPacket::SubAckPacket(ParsingInformation* parsingInformation, OnSubAckInternalCallback callback)
|
||||
: _parsingInformation(parsingInformation)
|
||||
, _callback(callback)
|
||||
, _bytePosition(0)
|
||||
, _packetIdMsb(0)
|
||||
, _packetId(0) {
|
||||
}
|
||||
|
||||
SubAckPacket::~SubAckPacket() {
|
||||
}
|
||||
|
||||
void SubAckPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) {
|
||||
char currentByte = data[(*currentBytePosition)++];
|
||||
if (_bytePosition++ == 0) {
|
||||
_packetIdMsb = currentByte;
|
||||
} else {
|
||||
_packetId = currentByte | _packetIdMsb << 8;
|
||||
_parsingInformation->bufferState = BufferState::PAYLOAD;
|
||||
}
|
||||
}
|
||||
|
||||
void SubAckPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) {
|
||||
char status = data[(*currentBytePosition)++];
|
||||
|
||||
/* switch (status) {
|
||||
case 0:
|
||||
Serial.println("Success QoS 0");
|
||||
break;
|
||||
case 1:
|
||||
Serial.println("Success QoS 1");
|
||||
break;
|
||||
case 2:
|
||||
Serial.println("Success QoS 2");
|
||||
break;
|
||||
case 0x80:
|
||||
Serial.println("Failure");
|
||||
break;
|
||||
} */
|
||||
|
||||
_parsingInformation->bufferState = BufferState::NONE;
|
||||
_callback(_packetId, status);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "Packet.hpp"
|
||||
#include "../ParsingInformation.hpp"
|
||||
#include "../Callbacks.hpp"
|
||||
|
||||
namespace AsyncMqttClientInternals {
|
||||
class SubAckPacket : public Packet {
|
||||
public:
|
||||
explicit SubAckPacket(ParsingInformation* parsingInformation, OnSubAckInternalCallback callback);
|
||||
~SubAckPacket();
|
||||
|
||||
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition);
|
||||
void parsePayload(char* data, size_t len, size_t* currentBytePosition);
|
||||
|
||||
private:
|
||||
ParsingInformation* _parsingInformation;
|
||||
OnSubAckInternalCallback _callback;
|
||||
|
||||
uint8_t _bytePosition;
|
||||
char _packetIdMsb;
|
||||
uint16_t _packetId;
|
||||
};
|
||||
} // namespace AsyncMqttClientInternals
|
||||
@@ -0,0 +1,30 @@
|
||||
#include "UnsubAckPacket.hpp"
|
||||
|
||||
using AsyncMqttClientInternals::UnsubAckPacket;
|
||||
|
||||
UnsubAckPacket::UnsubAckPacket(ParsingInformation* parsingInformation, OnUnsubAckInternalCallback callback)
|
||||
: _parsingInformation(parsingInformation)
|
||||
, _callback(callback)
|
||||
, _bytePosition(0)
|
||||
, _packetIdMsb(0)
|
||||
, _packetId(0) {
|
||||
}
|
||||
|
||||
UnsubAckPacket::~UnsubAckPacket() {
|
||||
}
|
||||
|
||||
void UnsubAckPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) {
|
||||
char currentByte = data[(*currentBytePosition)++];
|
||||
if (_bytePosition++ == 0) {
|
||||
_packetIdMsb = currentByte;
|
||||
} else {
|
||||
_packetId = currentByte | _packetIdMsb << 8;
|
||||
_parsingInformation->bufferState = BufferState::NONE;
|
||||
_callback(_packetId);
|
||||
}
|
||||
}
|
||||
|
||||
void UnsubAckPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) {
|
||||
(void)data;
|
||||
(void)currentBytePosition;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "Packet.hpp"
|
||||
#include "../ParsingInformation.hpp"
|
||||
#include "../Callbacks.hpp"
|
||||
|
||||
namespace AsyncMqttClientInternals {
|
||||
class UnsubAckPacket : public Packet {
|
||||
public:
|
||||
explicit UnsubAckPacket(ParsingInformation* parsingInformation, OnUnsubAckInternalCallback callback);
|
||||
~UnsubAckPacket();
|
||||
|
||||
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition);
|
||||
void parsePayload(char* data, size_t len, size_t* currentBytePosition);
|
||||
|
||||
private:
|
||||
ParsingInformation* _parsingInformation;
|
||||
OnUnsubAckInternalCallback _callback;
|
||||
|
||||
uint8_t _bytePosition;
|
||||
char _packetIdMsb;
|
||||
uint16_t _packetId;
|
||||
};
|
||||
} // namespace AsyncMqttClientInternals
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
namespace AsyncMqttClientInternals {
|
||||
enum class BufferState : uint8_t {
|
||||
NONE = 0,
|
||||
REMAINING_LENGTH = 2,
|
||||
VARIABLE_HEADER = 3,
|
||||
PAYLOAD = 4
|
||||
};
|
||||
|
||||
struct ParsingInformation {
|
||||
BufferState bufferState;
|
||||
|
||||
uint16_t maxTopicLength;
|
||||
char* topicBuffer;
|
||||
|
||||
uint8_t packetType;
|
||||
uint16_t packetFlags;
|
||||
uint32_t remainingLength;
|
||||
};
|
||||
} // namespace AsyncMqttClientInternals
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user