Compare commits

...

51 Commits

Author SHA1 Message Date
Damian Schneider
76cb2e9988 Improvements to heap-memory and PSRAM handling (#4791)
* Improved heap and PSRAM handling

- Segment `allocateData()` uses more elaborate DRAM checking to reduce fragmentation and allow for larger setups to run on low heap
- Segment data allocation fails if minimum contiguous block size runs low to keep the UI working
- Increased `MAX_SEGMENT_DATA` to account for better segment data handling
- Memory allocation functions try to keep enough DRAM for segment data
- Added constant `PSRAM_THRESHOLD` to improve PSARM usage
- Increase MIN_HEAP_SIZE to reduce risk of breaking UI due to low memory for JSON response
- ESP32 makes use of IRAM (no 8bit access) for pixeluffers, freeing up to 50kB of RAM
- Fix to properly get available heap on all platforms: added function `getFreeHeapSize()`
- Bugfix for effects that divide by SEGLEN: don't run FX in service() if segment is not active
-Syntax fix in AR: calloc() uses (numelements, size) as arguments

* Added new functions for allocation and heap checking

- added `allocate_buffer()` function that can be used to allocate large buffers: takes parameters to set preferred ram location, including 32bit accessible RAM on ESP32. Returns null if heap runs low or switches to PSRAM
- getFreeHeapSize() and getContiguousFreeHeap() helper functions for all platforms to correctly report free useable heap
- updated some constants
- updated segment data allocation to free the data if it is large

- replaced "psramsafe" variable with it's #ifdef: BOARD_HAS_PSRAM and made accomodating changes
- added some compile-time checks to handle invalid env. definitions
- updated all allocation functions and some of the logic behind them
- added use of fast RTC-Memory where available
- increased MIN_HEAP_SIZE for all systems (improved stability in tests)
- updated memory calculation in web-UI to account for required segment buffer
- added UI alerts if buffer allocation fails
- made getUsedSegmentData() non-private (used in buffer alloc function)
- changed MAX_SEGMENT_DATA
- added more detailed memory log to DEBUG output
- added debug output to buffer alloc function
2025-09-16 19:46:16 +02:00
Will Tatam
9d706010f5 Update WLED app links for Android and iOS 2025-09-14 19:05:47 +01:00
Will Tatam
97b20438fd Fix typo in 'Originally' in readme.md 2025-09-14 19:03:21 +01:00
Will Tatam
65913f990d Update project title and add creator attribution 2025-09-14 18:24:19 +01:00
Will Tatam
56d00357d3 testing thanks.dev 2025-09-14 18:12:03 +01:00
netmindz
1864e550e6 Update funding to include currently active maintainers 2025-09-14 15:32:37 +01:00
Damian Schneider
75f6de9dc2 bugfix in Colortwinkles
thx @blazoncek
2025-09-09 07:31:19 +02:00
Damian Schneider
16cfbf7500 Merge pull request #4913 from DedeHai/percentFX_UI_fix
fix ancient UI bug that hides the speed slider in percent FX
2025-09-07 20:34:57 +02:00
Soeren
aecac2c56c Add CORS proxy information to README 2025-09-07 13:15:28 +02:00
Damian Schneider
385504e6db fix ancient UI bug that hides the speed slider in percent FX 2025-09-05 19:53:15 +02:00
Will Miles
a0321170d0 Merge pull request #4859 from Liliputech/udp_name_sync_rework
new usermod hooks "onUdpPacket"
2025-09-04 22:29:21 -04:00
netmindz
d70018ae9f Merge pull request #4910 from willmmiles/async-mqtt-client-dep
Un-vendor AsyncMqttClient
2025-09-04 07:33:27 +01:00
Will Miles
9359f0b7fc Fix include style 2025-09-03 23:29:24 -04:00
Will Miles
be74196a62 Un-vendor AsyncMqttClient
Switch to the upstream version.  This fixes a memory leak when
re-connection fails.
2025-09-03 23:05:20 -04:00
netmindz
705f2035f4 Change npm install to npm ci for dependencies
Updated installation instructions for dependencies.
2025-09-03 20:42:15 +01:00
Damian Schneider
65efcb351e Merge pull request #4908 from wled/copilot/fix-4788
Fix 2D matrix generator preview not updating after population
2025-09-03 20:07:54 +02:00
copilot-swe-agent[bot]
72ad39d6a7 Fix 2D matrix generator preview update issue
Co-authored-by: DedeHai <6280424+DedeHai@users.noreply.github.com>
2025-09-03 17:51:00 +00:00
copilot-swe-agent[bot]
5950204d34 Initial plan 2025-09-03 17:41:50 +00:00
netmindz
46df9410b3 Merge pull request #4900 from wled/copilot/fix-4881
Fix changelog generation to exclude unresolved closed issues
2025-09-03 07:16:45 +01:00
copilot-swe-agent[bot]
b7e4cd0d9a Fix parameter naming for changelog generator configuration
Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com>
2025-09-02 07:54:14 +00:00
copilot-swe-agent[bot]
0becd61323 Fix changelog generation to exclude unresolved closed issues
Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com>
2025-09-02 07:52:13 +00:00
copilot-swe-agent[bot]
3f2e92c4c5 Initial plan 2025-09-02 07:45:32 +00:00
netmindz
666a59ff53 Update platformio_override configuration for esp32 2025-09-02 08:06:41 +01:00
netmindz
ce7ca3f2d2 Add pull_request trigger for usermods workflow 2025-09-02 07:35:51 +01:00
netmindz
87092ccb80 Correct message formatting in PR merge workflow
Fix formatting in Discord webhook message for PR merge.
2025-09-02 07:11:55 +01:00
Damian Schneider
a037d99469 added debug output on crash, removed whitespaces 2025-09-01 22:22:16 +02:00
Damian Schneider
8cc5d64819 Merge pull request #4853 from willmmiles/bootloop-platform-simplification
Bootloop detection platform factoring
2025-09-01 22:17:07 +02:00
netmindz
4c948cca13 Merge pull request #4860 from tarontop/main
support iotorero ethernet controller
2025-09-01 10:03:54 +01:00
Aiden
5cb8dc3978 Delete the same define 2025-09-01 11:05:31 +08:00
netmindz
8fc87aa17d Merge pull request #4844 from Arcitec/0_16-fix-sync
Fix broken Sync button after 0.15 refactor
2025-08-31 12:30:19 +01:00
Arthur Suzuki
c8757d45c8 fix more nitpicks comments 2025-08-30 04:12:46 +02:00
Arthur Suzuki
62fad4dcdf applied coderabbit suggestions 2025-08-30 01:52:36 +02:00
Liliputech
da7f107273 fix POV Display usermod (#4427)
* POV Display usermod

this usermod adds a new effect called "POV Image".
To get it to work:
- read the README :)
- upload a bmp image to the ESP filesystem using "/edit" url.
- select "POV Image" effect.
- set the filename (ie: "/myimage.bmp") as segment name.
- rotate the segment at approximately 20 RPM.
- enjoy the show!
* improve file extension checks
* improve README, remove PNGdec reference, clean usermod
* restrain to esp32 platform + reduce memory footprint with malloc
2025-08-29 20:42:54 +02:00
Damian Schneider
d5d7fde30f use video scaling instead of NPB luminance & new ABL (#4798)
* updated color scaling to preserve hue at low brightness resulting in much better colors
* replace NPBlg with NPB, moved brightness scaling to bus manager
* improved gamma table calculation: fixed mismatch in inverting gamma table calculation: inversion should now be as good as it gets
* code cleanup, fixed gamma being applied in unnecessary places

Improvements to ABL handling:
- removed strip level handling, ist now all done on bus level
- limiter now respects pixel mapping
- proper handling of white channel
- improved current estimation
- current is now always correctly reported to UI
- minimal FPS impact if the ABL is not limiting but slighly higher impact for global ABL limit due to double-scaling

- moved brightness scaling to BusDigital
- created new header file colors.h to be able to access color functions in bus-manager.
- updated colo_fade() with better video scaling to preserve hue's at low brightness
- added IRAM_ATTR to color_fade (negligible speed impact when compared to inline and benefits other functions)
- added IRAM_ATTR to color_blend as it is used a lot throughout the code, did not test speed impact but adding it to color_fade made it almost on-par with an inlined function

Additional changes:
- fixes for properly handling `scaledBri()` (by @blazoncek)
- also use bit-shift instead of division in blending for ESP8266
- improvements for faster "softlight" calculation in blending
- changed some variables to uint8_t to maybe let the compiler optimize better, uint8_t can be faster if read, store and set are all done in uint8_t, which is the case in the ones I changed
- various minor code formatting changes
2025-08-29 17:12:10 +02:00
Will Miles
6f914d79b1 Increase boot loop timeout
Any repeating crash that prevents a human from logging in and fixing
the config should be treated as a boot loop.  Increase the detection
timeout, so anything that's fast enough to preclude a user fix will
trigger the recovery behaviour.
2025-08-28 21:17:12 -04:00
Will Miles
dd13c2df47 Reset crash counter after long interval
Don't treat consecutive but infrequent crashes as bootloops.  The
bootloop recovery actions only make sense when there is no opportunity
for a user to reconfigure their system.

Suggested by @coderabbitai
2025-08-28 21:10:39 -04:00
Damian Schneider
8aeb9e1abe bugfix in PS pointer alignment
- bug was leading to crashes when heap is low
2025-08-28 18:57:11 +02:00
Damian Schneider
cfad0b8a52 bugfix to prevent "almost infinite" loops in palette blend (#4841)
this fixes a very long loop when an overflow was happening in palette blending.
- reset prevPaletteBlends to prevent overflow
- add safety check in case overflow should still happen in another combination (or in future changes)
2025-08-28 18:08:31 +02:00
Arthur Suzuki
a60be251d2 fix nitpicks from coderabbit 2025-08-28 10:56:57 +02:00
netmindz
f15c1fbca6 Merge pull request #4880 from wled/copilot/fix-4879
Fix pr-merge.yaml to include PR title and link in Discord notifications
2025-08-28 08:43:28 +01:00
Will Miles
46f3bc0ced Bootloop: Include soft wdt on ESP8266 2025-08-26 21:00:54 -04:00
Arthur Suzuki
f8ce5980a1 removed tabs and replace by space 2025-08-25 22:45:01 +02:00
Arthur Suzuki
4b5c3a396d applied suggestions from review 2025-08-25 22:22:51 +02:00
Arthur Suzuki
550b4d9dea fix comments for usermod hooks "onUdpPacket" 2025-08-24 15:54:45 +02:00
Liliputech
f3e3f585df usermod udp_name_sync : properly initialize packet if segment name is empty
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-08-24 13:52:39 +02:00
netmindz
2082b01a3c Apply suggestions from code review
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-08-21 08:53:11 +01:00
Aiden
8baa6a4616 support iotorero ethernet controller 2025-08-21 12:51:48 +08:00
Arthur Suzuki
4de6656bc4 new usermod hooks "onUdpPacket"
this new hooks will help you implement new and custom protocols in
usermods.
I've provided an example (see usermods/udp_name_sync).
The example will help you share the main segment name across different
WLED instances.
The segment name can be useful to sync with some effects like GIF
image or scrolling text.

If you define new packet format in your usermod, make sure it will
either not collide with already used version of wled udp packet :
- 0 is for udp sync
- 1 is for AudioReactive data
- 2 is for udp_name_sync :)

Also, the onUdpPacket will override "parseNotification" if it returns "true".
Have fun!
2025-08-21 01:00:22 +02:00
Will Miles
85d4db83ed Isolate platform differences in bootloop check
Separate the platform-specific code from the logic, so any future
changes can be made in fewer places.
2025-08-20 11:32:32 -04:00
Will Miles
5146926723 Use direct references to RTC RAM on ESP8266
ESP8266 RTC RAM requires 32-bit accesses, but there's no need to jump
through a bunch of functions for it.  Use references to simplify access
and harmonize the implementation with ESP32.
2025-08-20 10:22:42 -04:00
Arcitec
4ac7eb7eb2 Fix broken Sync button after 0.15 refactor
In the past, the "notify direct" flag controlled all network syncing, propagating all color changes to other devices on the network. Pressing the UI Sync button only toggled this flag, so "notify direct" was set to false by default.

In version 0.15, a separate "master" sync flag was introduced, and the UI Sync button now only activates this master flag. However, the rest of the flag defaults weren't configured to sync anything at all. As a result, users pressing Sync saw *no* syncing at all, leading to multiple bug reports.

Defaults are now user-friendly: Enabling Sync on a WLED device syncs all of *its* color changes, whether made via the UI, API or remote button, providing a consistent experience which matches the intended behavior from past WLED versions.

Philips Hue sync is now also disabled by default, making the stock defaults focused on WLED devices. Users with other RGB ecosystems can manually enable the Hue or Alexa syncing in the settings.
2025-08-17 18:12:22 +02:00
79 changed files with 1519 additions and 2876 deletions

3
.github/FUNDING.yml vendored
View File

@@ -1,2 +1,3 @@
github: [Aircoookie,blazoncek]
github: [Aircoookie,blazoncek,DedeHai,lost-hope,willmmiles]
custom: ['https://paypal.me/Aircoookie','https://paypal.me/blazoncek']
thanks_dev: u/gh/netmindz

View File

@@ -8,7 +8,7 @@ Always reference these instructions first and fallback to search or bash command
### Initial Setup
- Install Node.js 20+ (specified in `.nvmrc`): Check your version with `node --version`
- Install dependencies: `npm install` (takes ~5 seconds)
- Install dependencies: `npm ci` (takes ~5 seconds)
- Install PlatformIO for hardware builds: `pip install -r requirements.txt` (takes ~60 seconds)
### Build and Test Workflow
@@ -135,4 +135,4 @@ The GitHub Actions workflow:
4. Compiles firmware for multiple hardware targets
5. Uploads build artifacts
Match this workflow in your local development to ensure CI success.
Match this workflow in your local development to ensure CI success.

View File

@@ -27,6 +27,8 @@ 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:

View File

@@ -33,6 +33,6 @@
run: |
jq -n \
--arg content "Pull Request #${PR_NUMBER} \"${PR_TITLE}\" merged by ${ACTOR}
${PR_URL}" \
${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 }}

View File

@@ -24,7 +24,9 @@ jobs:
with:
token: ${{ secrets.GITHUB_TOKEN }}
sinceTag: v0.15.0
maxIssues: 500
maxIssues: 500
# Exclude issues that were closed without resolution from changelog
exclude-labels: 'stale,wontfix,duplicate,invalid'
- name: Create draft release
uses: softprops/action-gh-release@v1
with:

View File

@@ -5,6 +5,9 @@ on:
paths:
- usermods/**
- .github/workflows/usermods.yml
pull_request:
paths:
- usermods/**
jobs:

View File

@@ -143,6 +143,7 @@ lib_deps =
makuna/NeoPixelBus @ 2.8.3
#https://github.com/makuna/NeoPixelBus.git#CoreShaderBeta
https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.4.2
marvinroger/AsyncMqttClient @ 0.9.0
# for I2C interface
;Wire
# ESP-NOW library

View File

@@ -28,7 +28,6 @@ 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}

View File

@@ -10,10 +10,12 @@
</p>
# Welcome to my project WLED! ✨
# Welcome to 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
@@ -32,7 +34,7 @@ A fast and feature-rich implementation of an ESP32 and ESP8266 webserver to cont
- 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=com.aircoookie.WLED) and [iOS](https://apps.apple.com/us/app/wled/id1475695033)
- 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)
- JSON and HTTP request APIs
- MQTT
- E1.31, Art-Net, DDP and TPM2.net
@@ -63,6 +65,7 @@ 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!

View File

@@ -224,8 +224,8 @@ void FFTcode(void * parameter)
DEBUGSR_PRINT("FFT started on core: "); DEBUGSR_PRINTLN(xPortGetCoreID());
// allocate FFT buffers on first call
if (vReal == nullptr) vReal = (float*) calloc(sizeof(float), samplesFFT);
if (vImag == nullptr) vImag = (float*) calloc(sizeof(float), samplesFFT);
if (vReal == nullptr) vReal = (float*) calloc(samplesFFT, sizeof(float));
if (vImag == nullptr) vImag = (float*) calloc(samplesFFT, sizeof(float));
if ((vReal == nullptr) || (vImag == nullptr)) {
// something went wrong
if (vReal) free(vReal); vReal = nullptr;

View File

@@ -2,7 +2,7 @@
default_envs = usermods_esp32, usermods_esp32c3, usermods_esp32s2, usermods_esp32s3
[env:usermods_esp32]
extends = env:esp32dev_V4
extends = env:esp32dev
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

View File

@@ -0,0 +1,48 @@
## POV Display usermod
This usermod adds a new effect called “POV Image”.
![the usermod at work](pov_display.gif?raw=true)
###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 upsidedown, 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 24bit, 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 (casesensitive) and is a 24bit uncompressed BMP.
- Garbled colors or wrong orientation: reexport as 24bit 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.

View File

@@ -0,0 +1,146 @@
#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);
}

View File

@@ -0,0 +1,50 @@
#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

View File

@@ -1,7 +1,5 @@
{
"name:": "pov_display",
"build": { "libArchive": false},
"dependencies": {
"bitbank2/PNGdec":"^1.0.3"
}
"platforms": ["espressif32"]
}

View File

@@ -0,0 +1,47 @@
#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;
}

View File

@@ -0,0 +1,42 @@
#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 &image;}
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

View File

@@ -1,88 +1,75 @@
#include "wled.h"
#include <PNGdec.h>
#include "pov.h"
void * openFile(const char *filename, int32_t *size) {
f = WLED_FS.open(filename);
*size = f.size();
return &f;
}
static const char _data_FX_MODE_POV_IMAGE[] PROGMEM = "POV Image@!;;;;";
void closeFile(void *handle) {
if (f) f.close();
}
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 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();
}
static POV s_pov;
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;
}
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;
}
class PovDisplayUsermod : public Usermod
{
public:
static const char _data_FX_MODE_POV_IMAGE[] PROGMEM = "POV Image@!;;;1";
class PovDisplayUsermod : public Usermod {
protected:
bool enabled = false; //WLEDMM
const char *_name; //WLEDMM
bool initDone = false; //WLEDMM
unsigned long lastTime = 0; //WLEDMM
public:
PNG png;
File f;
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)
}
void setup() {
strip.addEffect(255, &mode_pov_image, _data_FX_MODE_POV_IMAGE);
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 loop() {
}
uint16_t getId()
{
return USERMOD_ID_POV_DISPLAY;
}
void connected() {}
uint16_t getId() override {
return USERMOD_ID_POV_DISPLAY;
}
};
static PovDisplayUsermod pov_display;
REGISTER_USERMOD(pov_display);
static PovDisplayUsermod pov_display("POV Display", false);
REGISTER_USERMOD(pov_display);

Binary file not shown.

After

Width:  |  Height:  |  Size: 988 KiB

View File

@@ -0,0 +1,5 @@
{
"name": "udp_name_sync",
"build": { "libArchive": false },
"dependencies": {}
}

View File

@@ -0,0 +1,85 @@
#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);

View File

@@ -2328,7 +2328,7 @@ uint16_t mode_colortwinkle() {
}
if (cur == prev) { //fix "stuck" pixels
color_add(col, col);
col = color_add(col, col);
SEGMENT.setPixelColor(i, col);
}
else SEGMENT.setPixelColor(i, col);
@@ -3940,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;!,!;!";
/*
@@ -7528,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 = gamma8(cos8_t(valueR));
valueG = gamma8(cos8_t(valueG));
valueB = gamma8(cos8_t(valueB));
valueR = cos8_t(valueR);
valueG = cos8_t(valueG);
valueB = cos8_t(valueB);
if(SEGMENT.palette == 0) {
// use RGB values (original color mode)

View File

@@ -88,23 +88,26 @@ extern byte realtimeMode; // used in getMappedPixelIndex()
#endif
#define FPS_CALC_SHIFT 7 // bit shift for fixed point math
/* each segment uses 82 bytes of SRAM memory, so if you're application fails because of
insufficient memory, decreasing MAX_NUM_SEGMENTS may help */
// heap memory limit for effects data, pixel buffers try to reserve it if PSRAM is available
#ifdef ESP8266
#define MAX_NUM_SEGMENTS 16
/* How much data bytes all segments combined may allocate */
#define MAX_SEGMENT_DATA 5120
#define MAX_SEGMENT_DATA (6*1024) // 6k by default
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
#define MAX_NUM_SEGMENTS 20
#define MAX_SEGMENT_DATA (MAX_NUM_SEGMENTS*512) // 10k by default (S2 is short on free RAM)
#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
#else
#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
#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
#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 / WS2812FX::getMaxSegments())
#define FAIR_DATA_PER_SEG (MAX_SEGMENT_DATA / MAX_NUM_SEGMENTS)
#define MIN_SHOW_DELAY (_frametime < 16 ? 8 : 15)
@@ -533,7 +536,6 @@ 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; }
@@ -600,8 +602,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)
pixels = static_cast<uint32_t*>(d_calloc(sizeof(uint32_t), length())); // error handling is also done in isActive()
// 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));
if (!pixels) {
DEBUGFX_PRINTLN(F("!!! Not enough RAM for pixel buffer !!!"));
extern byte errorFlag;
@@ -623,7 +625,7 @@ class Segment {
#endif
clearName();
deallocateData();
d_free(pixels);
p_free(pixels);
}
Segment& operator= (const Segment &orig); // copy assignment
@@ -646,7 +648,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() { d_free(name); name = nullptr; return *this; }
inline Segment &clearName() { p_free(name); name = nullptr; return *this; }
inline Segment &setName(const String &name) { return setName(name.c_str()); }
inline static unsigned vLength() { return Segment::_vLength; }
@@ -672,6 +674,7 @@ 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.
@@ -868,8 +871,8 @@ class WS2812FX {
}
~WS2812FX() {
d_free(_pixels);
d_free(_pixelCCT); // just in case
p_free(_pixels);
p_free(_pixelCCT); // just in case
d_free(customMappingTable);
_mode.clear();
_modeData.clear();

177
wled00/FX_fcn.cpp Normal file → Executable file
View File

@@ -68,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*>(d_malloc(sizeof(uint32_t) * orig.length()));
pixels = static_cast<uint32_t*>(allocate_buffer(orig.length() * sizeof(uint32_t), BFRALLOC_PREFER_PSRAM | BFRALLOC_NOBYTEACCESS));
if (pixels) {
memcpy(pixels, orig.pixels, sizeof(uint32_t) * orig.length());
if (orig.name) { name = static_cast<char*>(d_malloc(strlen(orig.name)+1)); if (name) strcpy(name, orig.name); }
if (orig.name) { name = static_cast<char*>(allocate_buffer(strlen(orig.name)+1, BFRALLOC_PREFER_PSRAM)); 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 !!!"));
@@ -97,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) { d_free(name); name = nullptr; }
if (name) { p_free(name); name = nullptr; }
if (_t) stopTransition(); // also erases _t
deallocateData();
d_free(pixels);
p_free(pixels);
// copy source
memcpy((void*)this, (void*)&orig, sizeof(Segment));
// erase pointers to allocated data
@@ -111,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*>(d_malloc(sizeof(uint32_t) * orig.length()));
pixels = static_cast<uint32_t*>(allocate_buffer(orig.length() * sizeof(uint32_t), BFRALLOC_PREFER_PSRAM | BFRALLOC_NOBYTEACCESS));
if (pixels) {
memcpy(pixels, orig.pixels, sizeof(uint32_t) * orig.length());
if (orig.name) { name = static_cast<char*>(d_malloc(strlen(orig.name)+1)); if (name) strcpy(name, orig.name); }
if (orig.name) { name = static_cast<char*>(allocate_buffer(strlen(orig.name)+1, BFRALLOC_PREFER_PSRAM)); 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 !!!"));
@@ -130,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) { d_free(name); name = nullptr; } // free old name
if (name) { p_free(name); name = nullptr; } // free old name
if (_t) stopTransition(); // also erases _t
deallocateData(); // free old runtime data
d_free(pixels); // free old pixel buffer
p_free(pixels); // free old pixel buffer
// move source data
memcpy((void*)this, (void*)&orig, sizeof(Segment));
orig.name = nullptr;
@@ -147,35 +147,38 @@ 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) {
//DEBUG_PRINTF_P(PSTR("-- Clearing data (%d): %p\n"), len, this);
memset(data, 0, len); // erase buffer if called during effect initialisation
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
}
}
return true;
else
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("!!! Not enough RAM: %d/%d !!!\n"), len, Segment::getUsedSegmentData());
DEBUG_PRINTF_P(PSTR("SegmentData limit reached: %d/%d\n"), len, Segment::getUsedSegmentData());
errorFlag = ERR_NORAM;
return false;
}
// prefer DRAM over SPI RAM on ESP32 since it is slow
if (data) {
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
}
}
else data = (byte*)d_malloc(len);
#endif
if (data) {
memset(data, 0, len); // erase buffer
Segment::addUsedSegmentData(len - _dataLen);
d_free(data); // free data and try to allocate again (segment buffer may be blocking contiguous heap)
Segment::addUsedSegmentData(-_dataLen); // subtract buffer size
}
data = static_cast<byte*>(allocate_buffer(len, BFRALLOC_PREFER_DRAM | BFRALLOC_CLEAR)); // prefer DRAM over PSRAM for speed
if (data) {
Segment::addUsedSegmentData(len);
_dataLen = len;
//DEBUG_PRINTF_P(PSTR("--- Allocated data (%p): %d/%d -> %p\n"), this, len, Segment::getUsedSegmentData(), data);
return true;
@@ -209,7 +212,11 @@ void Segment::deallocateData() {
void Segment::resetIfRequired() {
if (!reset || !isActive()) return;
//DEBUG_PRINTF_P(PSTR("-- Segment reset: %p\n"), this);
if (data && _dataLen > 0) memset(data, 0, _dataLen); // prevent heap fragmentation (just erase buffer instead of deallocateData())
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 (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;
@@ -282,6 +289,7 @@ 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];
@@ -368,6 +376,7 @@ 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
@@ -464,7 +473,7 @@ 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*>(d_malloc(sizeof(uint32_t) * length()));
pixels = static_cast<uint32_t*>(allocate_buffer(length() * sizeof(uint32_t), BFRALLOC_PREFER_PSRAM | BFRALLOC_NOBYTEACCESS));
if (!pixels) {
DEBUGFX_PRINTLN(F("!!! Not enough RAM for pixel buffer !!!"));
deallocateData();
@@ -579,8 +588,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) d_free(name); // free old name
name = static_cast<char*>(d_malloc(newLen+1));
if (name) p_free(name); // free old name
name = static_cast<char*>(allocate_buffer(newLen+1, BFRALLOC_PREFER_PSRAM));
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;
@@ -1175,7 +1184,10 @@ void WS2812FX::finalizeInit() {
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);
} 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);
}
}
busConfigs.clear();
busConfigs.shrink_to_fit();
@@ -1192,8 +1204,9 @@ 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(bri);
bus->setBrightness(scaledBri(bri));
}
BusManager::initializeABL(); // init brightness limiter
DEBUG_PRINTF_P(PSTR("Heap after buses: %d\n"), ESP.getFreeHeap());
Segment::maxWidth = _length;
@@ -1206,10 +1219,11 @@ 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!)
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)));
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));
DEBUG_PRINTF_P(PSTR("strip buffer size: %uB\n"), getLengthTotal() * sizeof(uint32_t));
DEBUG_PRINTF_P(PSTR("Heap after strip init: %uB\n"), ESP.getFreeHeap());
DEBUG_PRINTF_P(PSTR("Heap after strip init: %uB\n"), getFreeHeapSize());
}
void WS2812FX::service() {
@@ -1295,7 +1309,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; }
#ifdef CONFIG_IDF_TARGET_ESP32C3
#if defined(ESP8266) || defined(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]
@@ -1306,10 +1320,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)); }
#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
#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
#else
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
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
#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); }
@@ -1548,68 +1562,12 @@ void WS2812FX::blendSegment(const Segment &topSegment) const {
Segment::setClippingRect(0, 0); // disable clipping for overlays
}
// 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
if (!_pixels) {
DEBUGFX_PRINTLN(F("Error: no _pixels!"));
errorFlag = ERR_NORAM;
return; // no pixels allocated, nothing to show
}
unsigned long showNow = millis();
size_t diff = showNow - _lastShow;
@@ -1619,7 +1577,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*>(d_malloc(totalLen * sizeof(uint8_t))); // allocate CCT buffer if necessary
_pixelCCT = static_cast<uint8_t*>(allocate_buffer(totalLen * sizeof(uint8_t), BFRALLOC_PREFER_PSRAM)); // allocate CCT buffer if necessary, prefer PSRAM
if (_pixelCCT) memset(_pixelCCT, 127, totalLen); // set neutral (50:50) CCT
if (realtimeMode == REALTIME_MODE_INACTIVE || useMainSegmentOnly || realtimeOverride > REALTIME_OVERRIDE_NONE) {
@@ -1635,10 +1593,6 @@ 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)
@@ -1649,11 +1603,15 @@ void WS2812FX::show() {
if (_pixelCCT) { // cctFromRgb already exluded at allocation
if (i == 0 || _pixelCCT[i-1] != _pixelCCT[i]) BusManager::setSegmentCCT(_pixelCCT[i], correctWB);
}
BusManager::setPixelColor(getMappedPixelIndex(i), realtimeMode && arlsDisableGammaCorrection ? _pixels[i] : gamma32(_pixels[i]));
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);
}
Bus::setCCT(oldCCT); // restore old CCT for ABL adjustments
d_free(_pixelCCT);
p_free(_pixelCCT);
_pixelCCT = nullptr;
// some buses send asynchronously and this method will return before
@@ -1661,9 +1619,6 @@ 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
@@ -1728,7 +1683,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(b);
BusManager::setBrightness(scaledBri(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

View File

@@ -1118,7 +1118,7 @@ bool initParticleSystem2D(ParticleSystem2D *&PartSys, uint32_t requestedsources,
allocsuccess = true;
break; // allocation succeeded
}
numparticles /= 2; // cut number of particles in half and try again
numparticles = ((numparticles / 2) + 3) & ~0x03; // cut number of particles in half and try again, must be 4 byte aligned
PSPRINTLN(F("PS 2D alloc failed, trying with less particles..."));
}
if (!allocsuccess) {
@@ -1815,7 +1815,7 @@ bool initParticleSystem1D(ParticleSystem1D *&PartSys, const uint32_t requestedso
allocsuccess = true;
break; // allocation succeeded
}
numparticles /= 2; // cut number of particles in half and try again
numparticles = ((numparticles / 2) + 3) & ~0x03; // cut number of particles in half and try again, must be 4 byte aligned
PSPRINTLN(F("PS 1D alloc failed, trying with less particles..."));
}
if (!allocsuccess) {

View File

@@ -22,6 +22,7 @@
#include "core_esp8266_waveform.h"
#endif
#include "const.h"
#include "colors.h"
#include "pin_manager.h"
#include "bus_manager.h"
#include "bus_wrapper.h"
@@ -38,35 +39,29 @@ 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
// PSRAM allocation wrappers
#if !defined(ESP8266) && !defined(CONFIG_IDF_TARGET_ESP32C3)
// memory allocation wrappers
extern "C" {
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); }
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
// 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);
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
}
#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))))
@@ -144,6 +139,7 @@ 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)) {
@@ -186,80 +182,62 @@ 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!
// 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;
}
// 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
void BusDigital::estimateCurrent() {
uint32_t actualMilliampsPerLed = _milliAmpsPerLed;
if (_milliAmpsPerLed == 255) {
useWackyWS2815PowerModel = true;
// 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
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)
}
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;
}
void BusDigital::applyBriLimit(uint8_t newBri) {
// a newBri of 0 means calculate per-bus brightness limit
if (newBri == 0) {
if (_milliAmpsLimit == 0 || _milliAmpsTotal == 0) return; // ABL not used for this bus
newBri = 255;
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;
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;
}
} else {
busPowerSum += (r + g + b + w);
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
}
}
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
}
// 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;
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
if (newBri < 255) {
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++) {
// 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
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
}
}
_colorSum = 0; // reset for next frame
}
void BusDigital::show() {
if (!_valid) return;
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 {
@@ -267,12 +245,6 @@ 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) {
@@ -286,13 +258,25 @@ 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;
unsigned co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder);
const uint8_t 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 = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, pix, co),_bri);
uint32_t cOld = PolyBus::getPixelColor(_busPtr, _iType, pix, co);
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;
@@ -309,17 +293,17 @@ void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) {
PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, wwcw);
}
// returns original color if global buffering is enabled, else returns lossly restored color from bus
// 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 unsigned co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder);
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),_bri);
if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs
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);
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);
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;
@@ -471,10 +455,7 @@ 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);
uint8_t g = G(c);
uint8_t b = B(c);
uint8_t w = W(c);
uint8_t r = R(c), g = G(c), b = B(c), w = W(c);
switch (_type) {
case TYPE_ANALOG_1CH: //one channel (white), relies on auto white calculation
@@ -649,10 +630,7 @@ 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);
uint8_t g = G(c);
uint8_t b = B(c);
uint8_t w = W(c);
uint8_t r = R(c), g = G(c), b = B(c), w = W(c);
_data = bool(r|g|b|w) && bool(_bri) ? 0xFF : 0;
}
@@ -918,7 +896,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
@@ -964,13 +942,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() {
_gMilliAmpsUsed = 0;
applyABL(); // apply brightness limit, updates _gMilliAmpsUsed
for (auto &bus : busses) {
bus->show();
_gMilliAmpsUsed += bus->getUsedCurrent();
}
}
@@ -1003,6 +981,85 @@ 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; }
@@ -1018,3 +1075,4 @@ 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;

View File

@@ -238,7 +238,6 @@ 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;
@@ -250,6 +249,9 @@ 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();
@@ -262,8 +264,10 @@ class BusDigital : public Bus {
uint8_t _pins[2];
uint8_t _iType;
uint16_t _frequencykHz;
uint8_t _milliAmpsPerLed;
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
void *_busPtr;
static uint16_t _milliAmpsTotal; // is overwitten/recalculated on each show()
@@ -278,8 +282,6 @@ class BusDigital : public Bus {
}
return c;
}
uint8_t estimateCurrentAndLimitBri() const;
};
@@ -422,8 +424,8 @@ struct BusConfig {
};
//fine tune power estimation constants for your setup
//you can set it to 0 if the ESP is powered by USB and the LEDs by external
// 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
#ifndef MA_FOR_ESP
#ifdef ESP8266
#define MA_FOR_ESP 80 //how much mA does the ESP use (Wemos D1 about 80mA)
@@ -438,6 +440,7 @@ 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() ;
@@ -453,6 +456,8 @@ 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

View File

@@ -3,7 +3,7 @@
#define BusWrapper_h
//#define NPB_CONF_4STEP_CADENCE
#include "NeoPixelBusLg.h"
#include "NeoPixelBus.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 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)
#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)
//RGBW
#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)
#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)
//400Kbps
#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)
#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)
//TM1814 (RGBW)
#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>
#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>
//TM1829 (RGB)
#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>
#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>
//UCS8903
#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)
#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)
//UCS8904 RGBW
#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)
#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)
//APA106
#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)
#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)
//FW1906 GRBCW
#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
#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
//WS2805 GRBCW
#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
#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
//TM1914 (RGB)
#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>
#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>
//Sm16825 (RGBWC)
#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>
#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>
#endif
/*** ESP32 Neopixel methods ***/
@@ -245,84 +245,84 @@
#endif
//RGB
#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)
#define B_32_RN_NEO_3 NeoPixelBus<NeoGrbFeature, NeoEsp32RmtNWs2812xMethod> // ESP32, S2, S3, C3
//#define B_32_IN_NEO_3 NeoPixelBus<NeoGrbFeature, NeoEsp32I2sNWs2812xMethod> // ESP32 (dynamic I2S selection)
#define B_32_I2_NEO_3 NeoPixelBus<NeoGrbFeature, X1Ws2812xMethod> // ESP32, S2, S3 (automatic I2S selection, see typedef above)
#define B_32_IP_NEO_3 NeoPixelBus<NeoGrbFeature, X8Ws2812xMethod> // parallel I2S (ESP32, S2, S3)
//RGBW
#define B_32_RN_NEO_4 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
#define B_32_RN_NEO_4 NeoPixelBus<NeoGrbwFeature, NeoEsp32RmtNSk6812Method>
#define B_32_I2_NEO_4 NeoPixelBus<NeoGrbwFeature, X1Sk6812Method>
#define B_32_IP_NEO_4 NeoPixelBus<NeoGrbwFeature, X8Sk6812Method> // parallel I2S
//400Kbps
#define B_32_RN_400_3 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
#define B_32_RN_400_3 NeoPixelBus<NeoGrbFeature, NeoEsp32RmtN400KbpsMethod>
#define B_32_I2_400_3 NeoPixelBus<NeoGrbFeature, X1400KbpsMethod>
#define B_32_IP_400_3 NeoPixelBus<NeoGrbFeature, X8400KbpsMethod> // parallel I2S
//TM1814 (RGBW)
#define B_32_RN_TM1_4 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
#define B_32_RN_TM1_4 NeoPixelBus<NeoWrgbTm1814Feature, NeoEsp32RmtNTm1814Method>
#define B_32_I2_TM1_4 NeoPixelBus<NeoWrgbTm1814Feature, X1Tm1814Method>
#define B_32_IP_TM1_4 NeoPixelBus<NeoWrgbTm1814Feature, X8Tm1814Method> // parallel I2S
//TM1829 (RGB)
#define B_32_RN_TM2_3 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
#define B_32_RN_TM2_3 NeoPixelBus<NeoBrgFeature, NeoEsp32RmtNTm1829Method>
#define B_32_I2_TM2_3 NeoPixelBus<NeoBrgFeature, X1Tm1829Method>
#define B_32_IP_TM2_3 NeoPixelBus<NeoBrgFeature, X8Tm1829Method> // parallel I2S
//UCS8903
#define B_32_RN_UCS_3 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
#define B_32_RN_UCS_3 NeoPixelBus<NeoRgbUcs8903Feature, NeoEsp32RmtNWs2812xMethod>
#define B_32_I2_UCS_3 NeoPixelBus<NeoRgbUcs8903Feature, X1800KbpsMethod>
#define B_32_IP_UCS_3 NeoPixelBus<NeoRgbUcs8903Feature, X8800KbpsMethod> // parallel I2S
//UCS8904
#define B_32_RN_UCS_4 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
#define B_32_RN_UCS_4 NeoPixelBus<NeoRgbwUcs8904Feature, NeoEsp32RmtNWs2812xMethod>
#define B_32_I2_UCS_4 NeoPixelBus<NeoRgbwUcs8904Feature, X1800KbpsMethod>
#define B_32_IP_UCS_4 NeoPixelBus<NeoRgbwUcs8904Feature, X8800KbpsMethod>// parallel I2S
//APA106
#define B_32_RN_APA106_3 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
#define B_32_RN_APA106_3 NeoPixelBus<NeoGrbFeature, NeoEsp32RmtNApa106Method>
#define B_32_I2_APA106_3 NeoPixelBus<NeoGrbFeature, X1Apa106Method>
#define B_32_IP_APA106_3 NeoPixelBus<NeoGrbFeature, X8Apa106Method> // parallel I2S
//FW1906 GRBCW
#define B_32_RN_FW6_5 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
#define B_32_RN_FW6_5 NeoPixelBus<NeoGrbcwxFeature, NeoEsp32RmtNWs2812xMethod>
#define B_32_I2_FW6_5 NeoPixelBus<NeoGrbcwxFeature, X1800KbpsMethod>
#define B_32_IP_FW6_5 NeoPixelBus<NeoGrbcwxFeature, X8800KbpsMethod> // parallel I2S
//WS2805 RGBWC
#define B_32_RN_2805_5 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
#define B_32_RN_2805_5 NeoPixelBus<NeoGrbwwFeature, NeoEsp32RmtNWs2805Method>
#define B_32_I2_2805_5 NeoPixelBus<NeoGrbwwFeature, X1Ws2805Method>
#define B_32_IP_2805_5 NeoPixelBus<NeoGrbwwFeature, X8Ws2805Method> // parallel I2S
//TM1914 (RGB)
#define B_32_RN_TM1914_3 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
#define B_32_RN_TM1914_3 NeoPixelBus<NeoGrbTm1914Feature, NeoEsp32RmtNTm1914Method>
#define B_32_I2_TM1914_3 NeoPixelBus<NeoGrbTm1914Feature, X1Tm1914Method>
#define B_32_IP_TM1914_3 NeoPixelBus<NeoGrbTm1914Feature, X8Tm1914Method> // parallel I2S
//Sm16825 (RGBWC)
#define B_32_RN_SM16825_5 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
#define B_32_RN_SM16825_5 NeoPixelBus<NeoRgbcwSm16825eFeature, NeoEsp32RmtNWs2812xMethod>
#define B_32_I2_SM16825_5 NeoPixelBus<NeoRgbcwSm16825eFeature, X1Ws2812xMethod>
#define B_32_IP_SM16825_5 NeoPixelBus<NeoRgbcwSm16825eFeature, X8Ws2812xMethod> // parallel I2S
#endif
//APA102
#ifdef WLED_USE_ETHERNET
// fix for #2542 (by @BlackBird77)
#define B_HS_DOT_3 NeoPixelBusLg<DotStarBgrFeature, DotStarEsp32HspiHzMethod, NeoGammaNullMethod> //hardware HSPI (was DotStarEsp32DmaHspi5MhzMethod in NPB @ 2.6.9)
#define B_HS_DOT_3 NeoPixelBus<DotStarBgrFeature, DotStarEsp32HspiHzMethod> //hardware HSPI (was DotStarEsp32DmaHspi5MhzMethod in NPB @ 2.6.9)
#else
#define B_HS_DOT_3 NeoPixelBusLg<DotStarBgrFeature, DotStarSpiHzMethod, NeoGammaNullMethod> //hardware VSPI
#define B_HS_DOT_3 NeoPixelBus<DotStarBgrFeature, DotStarSpiHzMethod> //hardware VSPI
#endif
#define B_SS_DOT_3 NeoPixelBusLg<DotStarBgrFeature, DotStarMethod, NeoGammaNullMethod> //soft SPI
#define B_SS_DOT_3 NeoPixelBus<DotStarBgrFeature, DotStarMethod> //soft SPI
//LPD8806
#define B_HS_LPD_3 NeoPixelBusLg<Lpd8806GrbFeature, Lpd8806SpiHzMethod, NeoGammaNullMethod>
#define B_SS_LPD_3 NeoPixelBusLg<Lpd8806GrbFeature, Lpd8806Method, NeoGammaNullMethod>
#define B_HS_LPD_3 NeoPixelBus<Lpd8806GrbFeature, Lpd8806SpiHzMethod>
#define B_SS_LPD_3 NeoPixelBus<Lpd8806GrbFeature, Lpd8806Method>
//LPD6803
#define B_HS_LPO_3 NeoPixelBusLg<Lpd6803GrbFeature, Lpd6803SpiHzMethod, NeoGammaNullMethod>
#define B_SS_LPO_3 NeoPixelBusLg<Lpd6803GrbFeature, Lpd6803Method, NeoGammaNullMethod>
#define B_HS_LPO_3 NeoPixelBus<Lpd6803GrbFeature, Lpd6803SpiHzMethod>
#define B_SS_LPO_3 NeoPixelBus<Lpd6803GrbFeature, Lpd6803Method>
//WS2801
#ifdef WLED_USE_ETHERNET
#define B_HS_WS1_3 NeoPixelBusLg<NeoRbgFeature, Ws2801MethodBase<TwoWireHspiImple<SpiSpeedHz>>, NeoGammaNullMethod>
#define B_HS_WS1_3 NeoPixelBus<NeoRbgFeature, Ws2801MethodBase<TwoWireHspiImple<SpiSpeedHz>>>
#else
#define B_HS_WS1_3 NeoPixelBusLg<NeoRbgFeature, Ws2801SpiHzMethod, NeoGammaNullMethod>
#define B_HS_WS1_3 NeoPixelBus<NeoRbgFeature, Ws2801SpiHzMethod>
#endif
#define B_SS_WS1_3 NeoPixelBusLg<NeoRbgFeature, Ws2801Method, NeoGammaNullMethod>
#define B_SS_WS1_3 NeoPixelBus<NeoRbgFeature, Ws2801Method>
//P9813
#define B_HS_P98_3 NeoPixelBusLg<P9813BgrFeature, P9813SpiHzMethod, NeoGammaNullMethod>
#define B_SS_P98_3 NeoPixelBusLg<P9813BgrFeature, P9813Method, NeoGammaNullMethod>
#define B_HS_P98_3 NeoPixelBus<P9813BgrFeature, P9813SpiHzMethod>
#define B_SS_P98_3 NeoPixelBus<P9813BgrFeature, P9813Method>
// 48bit & 64bit to 24bit & 32bit RGB(W) conversion
#define toRGBW32(c) (RGBW32((c>>40)&0xFF, (c>>24)&0xFF, (c>>8)&0xFF, (c>>56)&0xFF))
@@ -896,102 +896,6 @@ 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) {

View File

@@ -201,7 +201,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
}
#endif
DEBUG_PRINTF_P(PSTR("Heap before buses: %d\n"), ESP.getFreeHeap());
DEBUG_PRINTF_P(PSTR("Heap before buses: %d\n"), getFreeHeapSize());
JsonArray ins = hw_led["ins"];
if (!ins.isNull()) {
int s = 0; // bus iterator

View File

@@ -8,7 +8,7 @@
* color blend function, based on FastLED blend function
* the calculation for each color is: result = (A*(amountOfA) + A + B*(amountOfB) + B) / 256 with amountOfA = 255 - amountOfB
*/
uint32_t color_blend(uint32_t color1, uint32_t color2, uint8_t blend) {
uint32_t IRAM_ATTR color_blend(uint32_t color1, uint32_t color2, uint8_t blend) {
// min / max blend checking is omitted: calls with 0 or 255 are rare, checking lowers overall performance
const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF; // mask for R and B channels or W and G if negated (poorman's SIMD; https://github.com/wled/WLED/pull/4568#discussion_r1986587221)
uint32_t rb1 = color1 & TWO_CHANNEL_MASK; // extract R & B channels from color1
@@ -64,26 +64,26 @@ uint32_t color_add(uint32_t c1, uint32_t c2, bool preserveCR)
* fades color toward black
* if using "video" method the resulting color will never become black unless it is already black
*/
uint32_t color_fade(uint32_t c1, uint8_t amount, bool video)
{
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;
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;
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
uint8_t quarterMax = maxc >> 2; // note: using half of max results in color artefacts
addRemains = r && r > quarterMax ? 0x00010000 : 0;
addRemains |= g && g > quarterMax ? 0x00000100 : 0;
addRemains |= b && b > quarterMax ? 0x00000001 : 0;
addRemains |= w ? 0x01000000 : 0;
}
const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF;
uint32_t rb = (((c1 & TWO_CHANNEL_MASK) * 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;
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;
}
/*
@@ -92,7 +92,7 @@ uint32_t 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
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)
@@ -104,8 +104,7 @@ uint32_t adjust_color(uint32_t rgb, uint32_t hueShift, uint32_t lighten, uint32_
}
// 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
}
@@ -120,16 +119,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 20% slower
red1 = (red1 * f1 + (unsigned)entry->r * f2) >> 8; // note: using color_blend() is 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 color_fade(c1, brightness)
// actually same as color_fade(), using color_fade() is slower
uint32_t scale = brightness + 1; // adjust for rounding (bitshift)
red1 = (red1 * scale) >> 8; // note: using color_fade() is 30% slower
red1 = (red1 * scale) >> 8;
green1 = (green1 * scale) >> 8;
blue1 = (blue1 * scale) >> 8;
blue1 = (blue1 * scale) >> 8;
}
return RGBW32(red1,green1,blue1,0);
}
@@ -589,10 +588,13 @@ uint8_t NeoGammaWLEDMethod::gammaT_inv[256];
void NeoGammaWLEDMethod::calcGammaTable(float gamma)
{
float gamma_inv = 1.0f / gamma; // inverse gamma
for (size_t i = 0; i < 256; i++) {
for (size_t i = 1; i < 256; i++) {
gammaT[i] = (int)(powf((float)i / 255.0f, gamma) * 255.0f + 0.5f);
gammaT_inv[i] = (int)(powf((float)i / 255.0f, gamma_inv) * 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[0] = 0;
gammaT_inv[0] = 0;
}
uint8_t IRAM_ATTR_YN NeoGammaWLEDMethod::Correct(uint8_t value)
@@ -601,21 +603,6 @@ uint8_t IRAM_ATTR_YN NeoGammaWLEDMethod::Correct(uint8_t value)
return gammaT[value];
}
// 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;

144
wled00/colors.h Normal file
View File

@@ -0,0 +1,144 @@
#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 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);
[[gnu::hot, gnu::pure]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video = false);
#endif

View File

@@ -546,8 +546,21 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
#endif
#endif
// minimum heap size required to process web requests
#define MIN_HEAP_SIZE 8192
// 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
// Web server limits
#ifdef ESP8266

View File

@@ -107,6 +107,7 @@ 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)

View File

@@ -195,7 +195,6 @@
if (isAna(t)) return 5; // analog
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 ch = 3*hasRGB(t) + hasW(t) + hasCCT(t);
let mul = 1;
if (isDig(t)) {
@@ -207,7 +206,7 @@
mul = 2;
}
}
return len * ch * mul + dbl;
return len * ch * mul + len * 4; // add 4 bytes per LED for segment buffer (TODO: how to account for global buffer?)
}
function UI(change=false)

View File

@@ -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>

View File

@@ -191,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(scaledBri(bri), false);
strip.setBrightness(bri, false);
stateUpdated(CALL_MODE_WS_SEND);
}
return;

View File

@@ -73,133 +73,6 @@ 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();
@@ -442,6 +315,7 @@ 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;}
@@ -481,6 +355,7 @@ 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);
@@ -559,35 +434,44 @@ 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
// PSRAM allocation wrappers
#if !defined(ESP8266) && !defined(CONFIG_IDF_TARGET_ESP32C3)
// memory allocation wrappers (util.cpp)
extern "C" {
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); }
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
// 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);
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
}
#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
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
inline size_t getFreeHeapSize() { return ESP.getFreeHeap(); } // returns free heap
inline size_t getContiguousFreeHeap() { return ESP.getMaxFreeBlockSize(); } // returns largest contiguous free block
#endif
#define BFRALLOC_NOBYTEACCESS (1 << 0) // ESP32 has 32bit accessible DRAM (usually ~50kB free) that must not be byte-accessed
#define BFRALLOC_PREFER_DRAM (1 << 1) // prefer DRAM over PSRAM
#define BFRALLOC_ENFORCE_DRAM (1 << 2) // use DRAM only, no PSRAM
#define BFRALLOC_PREFER_PSRAM (1 << 3) // prefer PSRAM over DRAM
#define BFRALLOC_ENFORCE_PSRAM (1 << 4) // use PSRAM if available, otherwise uses DRAM
#define BFRALLOC_CLEAR (1 << 5) // clear allocated buffer after allocation
void *allocate_buffer(size_t size, uint32_t type);
void handleBootLoop(); // detect and handle bootloops
#ifndef ESP8266

View File

@@ -422,8 +422,8 @@ bool handleFileRead(AsyncWebServerRequest* request, String path){
DEBUGFS_PRINT(F("WS FileRead: ")); DEBUGFS_PRINTLN(path);
if(path.endsWith("/")) path += "index.htm";
if(path.indexOf(F("sec")) > -1) return false;
#ifdef ARDUINO_ARCH_ESP32
if (psramSafe && psramFound() && path.endsWith(FPSTR(getPresetsFileName()))) {
#ifdef BOARD_HAS_PSRAM
if (path.endsWith(FPSTR(getPresetsFileName()))) {
size_t psize;
const uint8_t *presets = getPresetCache(psize);
if (presets) {

View File

@@ -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, gamma8(red), gamma8(green), gamma8(blue));
activeSeg->setPixelColorXY(outX + i, outY + j, red, green, blue);
}
}
}

View File

@@ -312,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(scaledBri(bri), true);
strip.setBrightness(bri, true);
// freeze and init to black
if (!seg.freeze) {
@@ -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")] = ESP.getMaxAllocHeap();
root[F("maxalloc")] = getContiguousFreeHeap();
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")] = ESP.getMaxFreeBlockSize();
root[F("maxalloc")] = getContiguousFreeHeap();
root[F("resetReason")] = (int)ESP.getResetInfoPtr()->reason;
#endif
root[F("lwip")] = LWIP_VERSION_MAJOR;
#endif
root[F("freeheap")] = ESP.getFreeHeap();
#if defined(ARDUINO_ARCH_ESP32)
if (psramFound()) root[F("psram")] = ESP.getFreePsram();
root[F("freeheap")] = getFreeHeapSize();
#if defined(BOARD_HAS_PSRAM)
root[F("psram")] = ESP.getFreePsram();
#endif
root[F("uptime")] = millis()/1000 + rolloverMillis*4294967;

View File

@@ -57,7 +57,7 @@ void toggleOnOff()
//scales the brightness with the briMultiplier factor
byte scaledBri(byte in)
{
unsigned val = ((uint16_t)in*briMultiplier)/100;
unsigned val = ((unsigned)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(scaledBri(briT));
strip.setBrightness(briT);
}
}

View File

@@ -1,877 +0,0 @@
#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, &currentBytePosition);
break;
case AsyncMqttClientInternals::BufferState::PAYLOAD:
_currentParsedPacket->parsePayload(data, len, &currentBytePosition);
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;
}
}

View File

@@ -1,6 +0,0 @@
#ifndef SRC_ASYNCMQTTCLIENT_H_
#define SRC_ASYNCMQTTCLIENT_H_
#include "AsyncMqttClient.hpp"
#endif // SRC_ASYNCMQTTCLIENT_H_

View File

@@ -1,166 +0,0 @@
#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();
};

View File

@@ -1,28 +0,0 @@
#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

View File

@@ -1,15 +0,0 @@
#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
};

View File

@@ -1,57 +0,0 @@
#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

View File

@@ -1,38 +0,0 @@
#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

View File

@@ -1,7 +0,0 @@
#pragma once
struct AsyncMqttClientMessageProperties {
uint8_t qos;
bool dup;
bool retain;
};

View File

@@ -1,30 +0,0 @@
#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;
}

View File

@@ -1,25 +0,0 @@
#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

View File

@@ -1,11 +0,0 @@
#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

View File

@@ -1,21 +0,0 @@
#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;
}

View File

@@ -1,21 +0,0 @@
#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

View File

@@ -1,30 +0,0 @@
#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;
}

View File

@@ -1,25 +0,0 @@
#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

View File

@@ -1,30 +0,0 @@
#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;
}

View File

@@ -1,25 +0,0 @@
#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

View File

@@ -1,30 +0,0 @@
#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;
}

View File

@@ -1,25 +0,0 @@
#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

View File

@@ -1,30 +0,0 @@
#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;
}

View File

@@ -1,25 +0,0 @@
#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

View File

@@ -1,91 +0,0 @@
#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);
}
}

View File

@@ -1,38 +0,0 @@
#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

View File

@@ -1,46 +0,0 @@
#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);
}

View File

@@ -1,25 +0,0 @@
#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

View File

@@ -1,30 +0,0 @@
#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;
}

View File

@@ -1,25 +0,0 @@
#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

View File

@@ -1,21 +0,0 @@
#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

View File

@@ -1,13 +0,0 @@
#pragma once
namespace AsyncMqttClientInternals {
struct PendingPubRel {
uint16_t packetId;
};
struct PendingAck {
uint8_t packetType;
uint8_t headerFlag;
uint16_t packetId;
};
} // namespace AsyncMqttClientInternals

View File

@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015 Marvin Roger
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,18 +0,0 @@
Async MQTT client for ESP8266 and ESP32 (Github: https://github.com/marvinroger/async-mqtt-client)
=============================
[![Build Status](https://img.shields.io/travis/marvinroger/async-mqtt-client/master.svg?style=flat-square)](https://travis-ci.org/marvinroger/async-mqtt-client)
An Arduino for ESP8266 and ESP32 asynchronous [MQTT](http://mqtt.org/) client implementation, built on [me-no-dev/ESPAsyncTCP (ESP8266)](https://github.com/me-no-dev/ESPAsyncTCP) | [me-no-dev/AsyncTCP (ESP32)](https://github.com/me-no-dev/AsyncTCP) .
## Features
* Compliant with the 3.1.1 version of the protocol
* Fully asynchronous
* Subscribe at QoS 0, 1 and 2
* Publish at QoS 0, 1 and 2
* SSL/TLS support
* Available in the [PlatformIO registry](http://platformio.org/lib/show/346/AsyncMqttClient)
## Requirements, installation and usage
The project is documented in the [/docs folder](docs).

View File

@@ -424,7 +424,7 @@ void realtimeLock(uint32_t timeoutMs, byte md)
}
// if strip is off (bri==0) and not already in RTM
if (briT == 0) {
strip.setBrightness(scaledBri(briLast), true);
strip.setBrightness(briLast, true);
}
}
@@ -434,14 +434,14 @@ void realtimeLock(uint32_t timeoutMs, byte md)
realtimeMode = md;
if (realtimeOverride) return;
if (arlsForceMaxBri) strip.setBrightness(scaledBri(255), true);
if (arlsForceMaxBri) strip.setBrightness(255, true);
if (briT > 0 && md == REALTIME_MODE_GENERIC) strip.show();
}
void exitRealtime() {
if (!realtimeMode) return;
if (realtimeOverride == REALTIME_OVERRIDE_ONCE) realtimeOverride = REALTIME_OVERRIDE_NONE;
strip.setBrightness(scaledBri(bri), true);
strip.setBrightness(bri, true);
realtimeTimeout = 0; // cancel realtime mode immediately
realtimeMode = REALTIME_MODE_INACTIVE; // inform UI immediately
realtimeIP[0] = 0;
@@ -565,93 +565,82 @@ void handleNotifications()
return;
}
if (!receiveDirect) return;
if (receiveDirect) {
//TPM2.NET
if (udpIn[0] == 0x9c) {
//WARNING: this code assumes that the final TMP2.NET payload is evenly distributed if using multiple packets (ie. frame size is constant)
//if the number of LEDs in your installation doesn't allow that, please include padding bytes at the end of the last packet
byte tpmType = udpIn[1];
if (tpmType == 0xaa) { //TPM2.NET polling, expect answer
sendTPM2Ack(); return;
}
if (tpmType != 0xda) return; //return if notTPM2.NET data
//TPM2.NET
if (udpIn[0] == 0x9c)
{
//WARNING: this code assumes that the final TMP2.NET payload is evenly distributed if using multiple packets (ie. frame size is constant)
//if the number of LEDs in your installation doesn't allow that, please include padding bytes at the end of the last packet
byte tpmType = udpIn[1];
if (tpmType == 0xaa) { //TPM2.NET polling, expect answer
sendTPM2Ack(); return;
realtimeIP = (isSupp) ? notifier2Udp.remoteIP() : notifierUdp.remoteIP();
realtimeLock(realtimeTimeoutMs, REALTIME_MODE_TPM2NET);
if (realtimeOverride) return;
tpmPacketCount++; //increment the packet count
if (tpmPacketCount == 1) tpmPayloadFrameSize = (udpIn[2] << 8) + udpIn[3]; //save frame size for the whole payload if this is the first packet
byte packetNum = udpIn[4]; //starts with 1!
byte numPackets = udpIn[5];
unsigned id = (tpmPayloadFrameSize/3)*(packetNum-1); //start LED
unsigned totalLen = strip.getLengthTotal();
for (size_t i = 6; i < tpmPayloadFrameSize + 4U && id < totalLen; i += 3, id++) {
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
}
if (tpmPacketCount == numPackets) { //reset packet count and show if all packets were received
tpmPacketCount = 0;
if (useMainSegmentOnly) strip.trigger();
else strip.show();
}
return;
}
if (tpmType != 0xda) return; //return if notTPM2.NET data
realtimeIP = (isSupp) ? notifier2Udp.remoteIP() : notifierUdp.remoteIP();
realtimeLock(realtimeTimeoutMs, REALTIME_MODE_TPM2NET);
if (realtimeOverride) return;
//UDP realtime: 1 warls 2 drgb 3 drgbw 4 dnrgb 5 dnrgbw
if (udpIn[0] > 0 && udpIn[0] < 6) {
realtimeIP = (isSupp) ? notifier2Udp.remoteIP() : notifierUdp.remoteIP();
DEBUG_PRINTLN(realtimeIP);
if (packetSize < 2) return;
tpmPacketCount++; //increment the packet count
if (tpmPacketCount == 1) tpmPayloadFrameSize = (udpIn[2] << 8) + udpIn[3]; //save frame size for the whole payload if this is the first packet
byte packetNum = udpIn[4]; //starts with 1!
byte numPackets = udpIn[5];
if (udpIn[1] == 0) {
realtimeTimeout = 0; // cancel realtime mode immediately
return;
} else {
realtimeLock(udpIn[1]*1000 +1, REALTIME_MODE_UDP);
}
if (realtimeOverride) return;
unsigned id = (tpmPayloadFrameSize/3)*(packetNum-1); //start LED
unsigned totalLen = strip.getLengthTotal();
for (size_t i = 6; i < tpmPayloadFrameSize + 4U && id < totalLen; i += 3, id++) {
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
}
if (tpmPacketCount == numPackets) { //reset packet count and show if all packets were received
tpmPacketCount = 0;
unsigned totalLen = strip.getLengthTotal();
if (udpIn[0] == 1 && packetSize > 5) { //warls
for (size_t i = 2; i < packetSize -3; i += 4) {
setRealtimePixel(udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3], 0);
}
} else if (udpIn[0] == 2 && packetSize > 4) { //drgb
for (size_t i = 2, id = 0; i < packetSize -2 && id < totalLen; i += 3, id++)
{
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
}
} else if (udpIn[0] == 3 && packetSize > 6) { //drgbw
for (size_t i = 2, id = 0; i < packetSize -3 && id < totalLen; i += 4, id++) {
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]);
}
} else if (udpIn[0] == 4 && packetSize > 7) { //dnrgb
unsigned id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00);
for (size_t i = 4; i < packetSize -2 && id < totalLen; i += 3, id++) {
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
}
} else if (udpIn[0] == 5 && packetSize > 8) { //dnrgbw
unsigned id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00);
for (size_t i = 4; i < packetSize -2 && id < totalLen; i += 4, id++) {
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]);
}
}
if (useMainSegmentOnly) strip.trigger();
else strip.show();
}
return;
}
//UDP realtime: 1 warls 2 drgb 3 drgbw 4 dnrgb 5 dnrgbw
if (udpIn[0] > 0 && udpIn[0] < 6)
{
realtimeIP = (isSupp) ? notifier2Udp.remoteIP() : notifierUdp.remoteIP();
DEBUG_PRINTLN(realtimeIP);
if (packetSize < 2) return;
if (udpIn[1] == 0) {
realtimeTimeout = 0; // cancel realtime mode immediately
return;
} else {
realtimeLock(udpIn[1]*1000 +1, REALTIME_MODE_UDP);
}
if (realtimeOverride) return;
unsigned totalLen = strip.getLengthTotal();
if (udpIn[0] == 1 && packetSize > 5) //warls
{
for (size_t i = 2; i < packetSize -3; i += 4)
{
setRealtimePixel(udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3], 0);
}
} else if (udpIn[0] == 2 && packetSize > 4) //drgb
{
for (size_t i = 2, id = 0; i < packetSize -2 && id < totalLen; i += 3, id++)
{
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
}
} else if (udpIn[0] == 3 && packetSize > 6) //drgbw
{
for (size_t i = 2, id = 0; i < packetSize -3 && id < totalLen; i += 4, id++)
{
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]);
}
} else if (udpIn[0] == 4 && packetSize > 7) //dnrgb
{
unsigned id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00);
for (size_t i = 4; i < packetSize -2 && id < totalLen; i += 3, id++)
{
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
}
} else if (udpIn[0] == 5 && packetSize > 8) //dnrgbw
{
unsigned id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00);
for (size_t i = 4; i < packetSize -2 && id < totalLen; i += 4, id++)
{
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]);
}
}
if (useMainSegmentOnly) strip.trigger();
else strip.show();
return;
}
// API over UDP
@@ -669,6 +658,8 @@ void handleNotifications()
}
releaseJSONBufferLock();
}
UsermodManager::onUdpPacket(udpIn, packetSize);
}

View File

@@ -68,6 +68,10 @@ bool UsermodManager::onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t
return false;
}
#endif
bool UsermodManager::onUdpPacket(uint8_t* payload, size_t len) {
for (auto mod = _usermod_table_begin; mod < _usermod_table_end; ++mod) if ((*mod)->onUdpPacket(payload, len)) return true;
return false;
}
void UsermodManager::onUpdateBegin(bool init) { for (auto mod = _usermod_table_begin; mod < _usermod_table_end; ++mod) (*mod)->onUpdateBegin(init); } // notify usermods that update is to begin
void UsermodManager::onStateChange(uint8_t mode) { for (auto mod = _usermod_table_begin; mod < _usermod_table_end; ++mod) (*mod)->onStateChange(mode); } // notify usermods that WLED state changed

View File

@@ -629,216 +629,325 @@ int32_t hw_random(int32_t lowerlimit, int32_t upperlimit) {
return hw_random(diff) + lowerlimit;
}
#if !defined(ESP8266) && !defined(CONFIG_IDF_TARGET_ESP32C3) // ESP8266 does not support PSRAM, ESP32-C3 does not have PSRAM
// p_x prefer PSRAM, d_x prefer DRAM
void *p_malloc(size_t size) {
int caps1 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
int caps2 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
if (psramSafe) {
if (heap_caps_get_free_size(caps2) > 3*MIN_HEAP_SIZE && size < 512) std::swap(caps1, caps2); // use DRAM for small alloactions & when heap is plenty
return heap_caps_malloc_prefer(size, 2, caps1, caps2); // otherwise prefer PSRAM if it exists
}
return heap_caps_malloc(size, caps2);
}
// PSRAM compile time checks to provide info for misconfigured env
#if defined(BOARD_HAS_PSRAM)
#if defined(IDF_TARGET_ESP32C3) || defined(ESP8266)
#error "ESP32-C3 and ESP8266 with PSRAM is not supported, please remove BOARD_HAS_PSRAM definition"
#else
// BOARD_HAS_PSRAM also means that compiler flag "-mfix-esp32-psram-cache-issue" has to be used
#warning "BOARD_HAS_PSRAM defined, make sure to use -mfix-esp32-psram-cache-issue to prevent issues on rev.1 ESP32 boards \
see https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/external-ram.html#esp32-rev-v1-0"
#endif
#else
#if !defined(IDF_TARGET_ESP32C3) && !defined(ESP8266)
#pragma message("BOARD_HAS_PSRAM not defined, not using PSRAM.")
#endif
#endif
void *p_realloc(void *ptr, size_t size) {
int caps1 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
int caps2 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
if (psramSafe) {
if (heap_caps_get_free_size(caps2) > 3*MIN_HEAP_SIZE && size < 512) std::swap(caps1, caps2); // use DRAM for small alloactions & when heap is plenty
return heap_caps_realloc_prefer(ptr, size, 2, caps1, caps2); // otherwise prefer PSRAM if it exists
// memory allocation functions with minimum free heap size check
#ifdef ESP8266
static void *validateFreeHeap(void *buffer) {
// make sure there is enough free heap left if buffer was allocated in DRAM region, free it if not
if (getContiguousFreeHeap() < MIN_HEAP_SIZE) {
free(buffer);
return nullptr;
}
return heap_caps_realloc(ptr, size, caps2);
}
// realloc with malloc fallback, original buffer is freed if realloc fails but not copied!
void *p_realloc_malloc(void *ptr, size_t size) {
void *newbuf = p_realloc(ptr, size); // try realloc first
if (newbuf) return newbuf; // realloc successful
p_free(ptr); // free old buffer if realloc failed
return p_malloc(size); // fallback to malloc
}
void *p_calloc(size_t count, size_t size) {
int caps1 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
int caps2 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
if (psramSafe) {
if (heap_caps_get_free_size(caps2) > 3*MIN_HEAP_SIZE && size < 512) std::swap(caps1, caps2); // use DRAM for small alloactions & when heap is plenty
return heap_caps_calloc_prefer(count, size, 2, caps1, caps2); // otherwise prefer PSRAM if it exists
}
return heap_caps_calloc(count, size, caps2);
return buffer;
}
void *d_malloc(size_t size) {
int caps1 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
int caps2 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
if (psramSafe) {
if (heap_caps_get_largest_free_block(caps1) < 3*MIN_HEAP_SIZE && size > MIN_HEAP_SIZE) std::swap(caps1, caps2); // prefer PSRAM for large alloactions & when DRAM is low
return heap_caps_malloc_prefer(size, 2, caps1, caps2); // otherwise prefer DRAM
}
return heap_caps_malloc(size, caps1);
// note: using "if (getContiguousFreeHeap() > MIN_HEAP_SIZE + size)" did perform worse in tests with regards to keeping heap healthy and UI working
void *buffer = malloc(size);
return validateFreeHeap(buffer);
}
void *d_realloc(void *ptr, size_t size) {
int caps1 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
int caps2 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
if (psramSafe) {
if (heap_caps_get_largest_free_block(caps1) < 3*MIN_HEAP_SIZE && size > MIN_HEAP_SIZE) std::swap(caps1, caps2); // prefer PSRAM for large alloactions & when DRAM is low
return heap_caps_realloc_prefer(ptr, size, 2, caps1, caps2); // otherwise prefer DRAM
void *d_calloc(size_t count, size_t size) {
void *buffer = calloc(count, size);
return validateFreeHeap(buffer);
}
// realloc with malloc fallback, note: on ESPS8266 there is no safe way to ensure MIN_HEAP_SIZE during realloc()s, free buffer and allocate new one
void *d_realloc_malloc(void *ptr, size_t size) {
//void *buffer = realloc(ptr, size);
//buffer = validateFreeHeap(buffer);
//if (buffer) return buffer; // realloc successful
//d_free(ptr); // free old buffer if realloc failed (or min heap was exceeded)
//return d_malloc(size); // fallback to malloc
free(ptr);
return d_malloc(size);
}
#else
static void *validateFreeHeap(void *buffer) {
// make sure there is enough free heap left if buffer was allocated in DRAM region, free it if not
// TODO: between allocate and free, heap can run low (async web access), only IDF V5 allows for a pre-allocation-check of all free blocks
if ((uintptr_t)buffer > SOC_DRAM_LOW && (uintptr_t)buffer < SOC_DRAM_HIGH && getContiguousFreeHeap() < MIN_HEAP_SIZE) {
free(buffer);
return nullptr;
}
return heap_caps_realloc(ptr, size, caps1);
return buffer;
}
void *d_malloc(size_t size) {
void *buffer;
#if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3)
// the newer ESP32 variants have byte-accessible fast RTC memory that can be used as heap, access speed is on-par with DRAM
// the system does prefer normal DRAM until full, since free RTC memory is ~7.5k only, its below the minimum heap threshold and needs to be allocated explicitly
// use RTC RAM for small allocations to improve fragmentation or if DRAM is running low
if (size < 256 || getContiguousFreeHeap() < 2*MIN_HEAP_SIZE + size)
buffer = heap_caps_malloc_prefer(size, 2, MALLOC_CAP_RTCRAM, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
else
#endif
buffer = heap_caps_malloc(size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); // allocate in any available heap memory
buffer = validateFreeHeap(buffer); // make sure there is enough free heap left
#ifdef BOARD_HAS_PSRAM
if (!buffer)
return heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); // DRAM failed, use PSRAM if available
#endif
return buffer;
}
void *d_calloc(size_t count, size_t size) {
void *buffer = d_malloc(count * size);
if (buffer) memset(buffer, 0, count * size); // clear allocated buffer
return buffer;
}
// realloc with malloc fallback, original buffer is freed if realloc fails but not copied!
void *d_realloc_malloc(void *ptr, size_t size) {
void *newbuf = d_realloc(ptr, size); // try realloc first
if (newbuf) return newbuf; // realloc successful
d_free(ptr); // free old buffer if realloc failed
void *buffer = heap_caps_realloc(ptr, size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
buffer = validateFreeHeap(buffer);
if (buffer) return buffer; // realloc successful
d_free(ptr); // free old buffer if realloc failed (or min heap was exceeded)
return d_malloc(size); // fallback to malloc
}
void *d_calloc(size_t count, size_t size) {
int caps1 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
int caps2 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
if (psramSafe) {
if (size > MIN_HEAP_SIZE) std::swap(caps1, caps2); // prefer PSRAM for large alloactions
return heap_caps_calloc_prefer(count, size, 2, caps1, caps2); // otherwise prefer DRAM
}
return heap_caps_calloc(count, size, caps1);
#ifdef BOARD_HAS_PSRAM
// p_xalloc: prefer PSRAM, use DRAM as fallback
void *p_malloc(size_t size) {
void *buffer = heap_caps_malloc_prefer(size, 2, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
return validateFreeHeap(buffer);
}
#else // ESP8266 & ESP32-C3
void *p_calloc(size_t count, size_t size) {
void *buffer = p_malloc(count * size);
if (buffer) memset(buffer, 0, count * size); // clear allocated buffer
return buffer;
}
// realloc with malloc fallback, original buffer is freed if realloc fails but not copied!
void *realloc_malloc(void *ptr, size_t size) {
void *newbuf = realloc(ptr, size); // try realloc first
if (newbuf) return newbuf; // realloc successful
free(ptr); // free old buffer if realloc failed
return malloc(size); // fallback to malloc
void *p_realloc_malloc(void *ptr, size_t size) {
void *buffer = heap_caps_realloc(ptr, size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (buffer) return buffer; // realloc successful
p_free(ptr); // free old buffer if realloc failed
return p_malloc(size); // fallback to malloc
}
#endif
#endif
// allocation function for buffers like pixel-buffers and segment data
// optimises the use of memory types to balance speed and heap availability, always favours DRAM if possible
// if multiple conflicting types are defined, the lowest bits of "type" take priority (see fcn_declare.h for types)
void *allocate_buffer(size_t size, uint32_t type) {
void *buffer = nullptr;
#ifdef CONFIG_IDF_TARGET_ESP32
// only classic ESP32 has "32bit accessible only" aka IRAM type. Using it frees up normal DRAM for other purposes
// this memory region is used for IRAM_ATTR functions, whatever is left is unused and can be used for pixel buffers
// prefer this type over PSRAM as it is slightly faster, except for _pixels where it is on-par as PSRAM-caching does a good job for mostly sequential access
if (type & BFRALLOC_NOBYTEACCESS) {
// prefer 32bit region, then PSRAM, fallback to any heap. Note: if adding "INTERNAL"-flag this wont work
buffer = heap_caps_malloc_prefer(size, 3, MALLOC_CAP_32BIT, MALLOC_CAP_SPIRAM, MALLOC_CAP_8BIT);
buffer = validateFreeHeap(buffer);
}
else
#endif
#if !defined(BOARD_HAS_PSRAM)
buffer = d_malloc(size);
#else
if (type & BFRALLOC_PREFER_DRAM) {
if (getContiguousFreeHeap() < 3*(MIN_HEAP_SIZE/2) + size && size > PSRAM_THRESHOLD)
buffer = p_malloc(size); // prefer PSRAM for large allocations & when DRAM is low
else
buffer = d_malloc(size); // allocate in DRAM if enough free heap is available, PSRAM as fallback
}
else if (type & BFRALLOC_ENFORCE_DRAM)
buffer = heap_caps_malloc(size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); // use DRAM only, otherwise return nullptr
else if (type & BFRALLOC_PREFER_PSRAM) {
// if DRAM is plenty, prefer it over PSRAM for speed, reserve enough DRAM for segment data: if MAX_SEGMENT_DATA is exceeded, always uses PSRAM
if (getContiguousFreeHeap() > 4*MIN_HEAP_SIZE + size + ((uint32_t)(MAX_SEGMENT_DATA - Segment::getUsedSegmentData())))
buffer = d_malloc(size);
else
buffer = p_malloc(size); // prefer PSRAM
}
else if (type & BFRALLOC_ENFORCE_PSRAM)
buffer = heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); // use PSRAM only, otherwise return nullptr
buffer = validateFreeHeap(buffer);
#endif
if (buffer && (type & BFRALLOC_CLEAR))
memset(buffer, 0, size); // clear allocated buffer
/*
#if !defined(ESP8266) && defined(WLED_DEBUG)
if (buffer) {
DEBUG_PRINTF_P(PSTR("*Buffer allocated: size:%d, address:%p"), size, (uintptr_t)buffer);
if ((uintptr_t)buffer > SOC_DRAM_LOW && (uintptr_t)buffer < SOC_DRAM_HIGH)
DEBUG_PRINTLN(F(" in DRAM"));
#ifndef CONFIG_IDF_TARGET_ESP32C3
else if ((uintptr_t)buffer > SOC_EXTRAM_DATA_LOW && (uintptr_t)buffer < SOC_EXTRAM_DATA_HIGH)
DEBUG_PRINTLN(F(" in PSRAM"));
#endif
#ifdef CONFIG_IDF_TARGET_ESP32
else if ((uintptr_t)buffer > SOC_IRAM_LOW && (uintptr_t)buffer < SOC_IRAM_HIGH)
DEBUG_PRINTLN(F(" in IRAM")); // only used on ESP32 (MALLOC_CAP_32BIT)
#else
else if ((uintptr_t)buffer > SOC_RTC_DRAM_LOW && (uintptr_t)buffer < SOC_RTC_DRAM_HIGH)
DEBUG_PRINTLN(F(" in RTCRAM")); // not available on ESP32
#endif
else
DEBUG_PRINTLN(F(" in ???")); // unknown (check soc.h for other memory regions)
} else
DEBUG_PRINTF_P(PSTR("Buffer allocation failed: size:%d\n"), size);
#endif
*/
return buffer;
}
// bootloop detection and handling
// checks if the ESP reboots multiple times due to a crash or watchdog timeout
// if a bootloop is detected: restore settings from backup, then reset settings, then switch boot image (and repeat)
#define BOOTLOOP_THRESHOLD 5 // number of consecutive crashes to trigger bootloop detection
#define BOOTLOOP_ACTION_RESTORE 0 // default action: restore config from /bak.cfg.json
#define BOOTLOOP_ACTION_RESET 1 // if restore does not work, reset config (rename /cfg.json to /rst.cfg.json)
#define BOOTLOOP_ACTION_OTA 2 // swap the boot partition
#define BOOTLOOP_ACTION_DUMP 3 // nothing seems to help, dump files to serial and reboot (until hardware reset)
#define BOOTLOOP_INTERVAL_MILLIS 120000 // time limit between crashes: 120 seconds (2 minutes)
#define BOOTLOOP_THRESHOLD 5 // number of consecutive crashes to trigger bootloop detection
#define BOOTLOOP_ACTION_RESTORE 0 // default action: restore config from /bkp.cfg.json
#define BOOTLOOP_ACTION_RESET 1 // if restore does not work, reset config (rename /cfg.json to /rst.cfg.json)
#define BOOTLOOP_ACTION_OTA 2 // swap the boot partition
#define BOOTLOOP_ACTION_DUMP 3 // nothing seems to help, dump files to serial and reboot (until hardware reset)
// Platform-agnostic abstraction
enum class ResetReason {
Power,
Software,
Crash,
Brownout
};
#ifdef ESP8266
#define BOOTLOOP_INTERVAL_TICKS (5 * 160000) // time limit between crashes: ~5 seconds in RTC ticks
#define BOOT_TIME_IDX 0 // index in RTC memory for boot time
#define CRASH_COUNTER_IDX 1 // index in RTC memory for crash counter
#define ACTIONT_TRACKER_IDX 2 // index in RTC memory for boot action
// Place variables in RTC memory via references, since RTC memory is not exposed via the linker in the Non-OS SDK
// Use an offset of 32 as there's some hints that the first 128 bytes of "user" memory are used by the OTA system
// Ref: https://github.com/esp8266/Arduino/blob/78d0d0aceacc1553f45ad8154592b0af22d1eede/cores/esp8266/Esp.cpp#L168
static volatile uint32_t& bl_last_boottime = *(RTC_USER_MEM + 32);
static volatile uint32_t& bl_crashcounter = *(RTC_USER_MEM + 33);
static volatile uint32_t& bl_actiontracker = *(RTC_USER_MEM + 34);
static inline ResetReason rebootReason() {
uint32_t resetReason = system_get_rst_info()->reason;
if (resetReason == REASON_EXCEPTION_RST
|| resetReason == REASON_WDT_RST
|| resetReason == REASON_SOFT_WDT_RST)
return ResetReason::Crash;
if (resetReason == REASON_SOFT_RESTART)
return ResetReason::Software;
return ResetReason::Power;
}
static inline uint32_t getRtcMillis() { return system_get_rtc_time() / 160; }; // rtc ticks ~160000Hz
#else
#define BOOTLOOP_INTERVAL_TICKS 5000 // time limit between crashes: ~5 seconds in milliseconds
// variables in RTC_NOINIT memory persist between reboots (but not on hardware reset)
RTC_NOINIT_ATTR static uint32_t bl_last_boottime;
RTC_NOINIT_ATTR static uint32_t bl_crashcounter;
RTC_NOINIT_ATTR static uint32_t bl_actiontracker;
static inline ResetReason rebootReason() {
esp_reset_reason_t reason = esp_reset_reason();
if (reason == ESP_RST_BROWNOUT) return ResetReason::Brownout;
if (reason == ESP_RST_SW) return ResetReason::Software;
if (reason == ESP_RST_PANIC || reason == ESP_RST_WDT || reason == ESP_RST_INT_WDT || reason == ESP_RST_TASK_WDT) return ResetReason::Crash;
return ResetReason::Power;
}
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)
static inline uint32_t getRtcMillis() { return esp_rtc_get_time_us() / 1000; }
#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(3, 3, 0)
static inline uint32_t getRtcMillis() { return rtc_time_slowclk_to_us(rtc_time_get(), rtc_clk_slow_freq_get_hz()) / 1000; }
#endif
void bootloopCheckOTA() { bl_actiontracker = BOOTLOOP_ACTION_OTA; } // swap boot image if bootloop is detected instead of restoring config
#endif
// detect bootloop by checking the reset reason and the time since last boot
static bool detectBootLoop() {
#if !defined(ESP8266)
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)
uint32_t rtctime = esp_rtc_get_time_us() / 1000; // convert to milliseconds
#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(3, 3, 0)
uint64_t rtc_ticks = rtc_time_get();
uint32_t rtctime = rtc_time_slowclk_to_us(rtc_ticks, rtc_clk_slow_freq_get_hz()) / 1000; // convert to milliseconds
#endif
uint32_t rtctime = getRtcMillis();
bool result = false;
esp_reset_reason_t reason = esp_reset_reason();
if (!(reason == ESP_RST_PANIC || reason == ESP_RST_WDT || reason == ESP_RST_INT_WDT || reason == ESP_RST_TASK_WDT)) {
// no crash detected, init variables
bl_crashcounter = 0;
bl_last_boottime = rtctime;
if(reason != ESP_RST_SW)
switch(rebootReason()) {
case ResetReason::Power:
bl_actiontracker = BOOTLOOP_ACTION_RESTORE; // init action tracker if not an intentional reboot (e.g. from OTA or bootloop handler)
} else if (reason == ESP_RST_BROWNOUT) {
// crash due to brownout can't be detected unless using flash memory to store bootloop variables
// this is a simpler way to preemtively revert the config in case current brownout is caused by a bad choice of settings
DEBUG_PRINTLN(F("brownout detected"));
//restoreConfig(); // TODO: blindly restoring config if brownout detected is a bad idea, need a better way (if at all)
} else {
uint32_t rebootinterval = rtctime - bl_last_boottime;
bl_last_boottime = rtctime; // store current runtime for next reboot
if (rebootinterval < BOOTLOOP_INTERVAL_TICKS) {
bl_crashcounter++;
if (bl_crashcounter >= BOOTLOOP_THRESHOLD) {
DEBUG_PRINTLN(F("!BOOTLOOP DETECTED!"));
bl_crashcounter = 0;
return true;
}
}
}
#else // ESP8266
rst_info* resetreason = system_get_rst_info();
uint32_t bl_last_boottime;
uint32_t bl_crashcounter;
uint32_t bl_actiontracker;
uint32_t rtctime = system_get_rtc_time();
// fall through
case ResetReason::Software:
// no crash detected, reset counter
bl_crashcounter = 0;
break;
if (!(resetreason->reason == REASON_EXCEPTION_RST || resetreason->reason == REASON_WDT_RST)) {
// no crash detected, init variables
bl_crashcounter = 0;
ESP.rtcUserMemoryWrite(BOOT_TIME_IDX, &rtctime, sizeof(uint32_t));
ESP.rtcUserMemoryWrite(CRASH_COUNTER_IDX, &bl_crashcounter, sizeof(uint32_t));
if(resetreason->reason != REASON_SOFT_RESTART) {
bl_actiontracker = BOOTLOOP_ACTION_RESTORE; // init action tracker if not an intentional reboot (e.g. from OTA or bootloop handler)
ESP.rtcUserMemoryWrite(ACTIONT_TRACKER_IDX, &bl_actiontracker, sizeof(uint32_t));
}
} else {
// system has crashed
ESP.rtcUserMemoryRead(BOOT_TIME_IDX, &bl_last_boottime, sizeof(uint32_t));
ESP.rtcUserMemoryRead(CRASH_COUNTER_IDX, &bl_crashcounter, sizeof(uint32_t));
uint32_t rebootinterval = rtctime - bl_last_boottime;
ESP.rtcUserMemoryWrite(BOOT_TIME_IDX, &rtctime, sizeof(uint32_t)); // store current ticks for next reboot
if (rebootinterval < BOOTLOOP_INTERVAL_TICKS) {
bl_crashcounter++;
ESP.rtcUserMemoryWrite(CRASH_COUNTER_IDX, &bl_crashcounter, sizeof(uint32_t));
if (bl_crashcounter >= BOOTLOOP_THRESHOLD) {
DEBUG_PRINTLN(F("BOOTLOOP DETECTED"));
case ResetReason::Crash:
{
DEBUG_PRINTLN(F("crash detected!"));
uint32_t rebootinterval = rtctime - bl_last_boottime;
if (rebootinterval < BOOTLOOP_INTERVAL_MILLIS) {
bl_crashcounter++;
if (bl_crashcounter >= BOOTLOOP_THRESHOLD) {
DEBUG_PRINTLN(F("!BOOTLOOP DETECTED!"));
bl_crashcounter = 0;
result = true;
}
} else {
// Reset counter on long intervals to track only consecutive short-interval crashes
bl_crashcounter = 0;
ESP.rtcUserMemoryWrite(CRASH_COUNTER_IDX, &bl_crashcounter, sizeof(uint32_t));
return true;
// TODO: crash reporting goes here
}
break;
}
case ResetReason::Brownout:
// crash due to brownout can't be detected unless using flash memory to store bootloop variables
DEBUG_PRINTLN(F("brownout detected"));
//restoreConfig(); // TODO: blindly restoring config if brownout detected is a bad idea, need a better way (if at all)
break;
}
#endif
return false; // no bootloop detected
bl_last_boottime = rtctime; // store current runtime for next reboot
return result;
}
void handleBootLoop() {
DEBUG_PRINTLN(F("checking for bootloop"));
DEBUG_PRINTF_P(PSTR("checking for bootloop: time %d, counter %d, action %d\n"), bl_last_boottime, bl_crashcounter, bl_actiontracker);
if (!detectBootLoop()) return; // no bootloop detected
#ifdef ESP8266
uint32_t bl_actiontracker;
ESP.rtcUserMemoryRead(ACTIONT_TRACKER_IDX, &bl_actiontracker, sizeof(uint32_t));
#endif
if (bl_actiontracker == BOOTLOOP_ACTION_RESTORE) {
restoreConfig(); // note: if this fails, could reset immediately. instead just let things play out and save a few lines of code
bl_actiontracker = BOOTLOOP_ACTION_RESET; // reset config if it keeps bootlooping
} else if (bl_actiontracker == BOOTLOOP_ACTION_RESET) {
resetConfig();
bl_actiontracker = BOOTLOOP_ACTION_OTA; // swap boot partition if it keeps bootlooping. On ESP8266 this is the same as BOOTLOOP_ACTION_NONE
}
switch(bl_actiontracker) {
case BOOTLOOP_ACTION_RESTORE:
restoreConfig();
++bl_actiontracker;
break;
case BOOTLOOP_ACTION_RESET:
resetConfig();
++bl_actiontracker;
break;
case BOOTLOOP_ACTION_OTA:
#ifndef ESP8266
else if (bl_actiontracker == BOOTLOOP_ACTION_OTA) {
if(Update.canRollBack()) {
DEBUG_PRINTLN(F("Swapping boot partition..."));
Update.rollBack(); // swap boot partition
}
bl_actiontracker = BOOTLOOP_ACTION_DUMP; // out of options
}
#endif
else
dumpFilesToSerial();
#ifdef ESP8266
ESP.rtcUserMemoryWrite(ACTIONT_TRACKER_IDX, &bl_actiontracker, sizeof(uint32_t));
if(Update.canRollBack()) {
DEBUG_PRINTLN(F("Swapping boot partition..."));
Update.rollBack(); // swap boot partition
}
++bl_actiontracker;
break;
#else
// fall through
#endif
case BOOTLOOP_ACTION_DUMP:
dumpFilesToSerial();
break;
}
ESP.restart(); // restart cleanly and don't wait for another crash
}

View File

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

View File

@@ -155,7 +155,7 @@
#include "src/dependencies/e131/ESPAsyncE131.h"
#ifndef WLED_DISABLE_MQTT
#include "src/dependencies/async-mqtt-client/AsyncMqttClient.h"
#include <AsyncMqttClient.h>
#endif
#define ARDUINOJSON_DECODE_UNICODE 0
@@ -167,16 +167,13 @@
// The following is a construct to enable code to compile without it.
// There is a code that will still not use PSRAM though:
// AsyncJsonResponse is a derived class that implements DynamicJsonDocument (AsyncJson-v6.h)
#if defined(ARDUINO_ARCH_ESP32)
extern bool psramSafe;
#if defined(BOARD_HAS_PSRAM)
struct PSRAM_Allocator {
void* allocate(size_t size) {
if (psramSafe && psramFound()) return ps_malloc(size); // use PSRAM if it exists
else return malloc(size); // fallback
return ps_malloc(size); // use PSRAM
}
void* reallocate(void* ptr, size_t new_size) {
if (psramSafe && psramFound()) return ps_realloc(ptr, new_size); // use PSRAM if it exists
else return realloc(ptr, new_size); // fallback
return ps_realloc(ptr, new_size); // use PSRAM
}
void deallocate(void* pointer) {
free(pointer);
@@ -194,6 +191,7 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument<PSRAM_Allocator>;
#include "fcn_declare.h"
#include "NodeStruct.h"
#include "pin_manager.h"
#include "colors.h"
#include "bus_manager.h"
#include "FX.h"
@@ -733,10 +731,10 @@ WLED_GLOBAL bool receiveNotificationPalette _INIT(true); // apply palet
WLED_GLOBAL bool receiveSegmentOptions _INIT(false); // apply segment options
WLED_GLOBAL bool receiveSegmentBounds _INIT(false); // apply segment bounds (start, stop, offset)
WLED_GLOBAL bool receiveDirect _INIT(true); // receive UDP/Hyperion realtime
WLED_GLOBAL bool notifyDirect _INIT(false); // send notification if change via UI or HTTP API
WLED_GLOBAL bool notifyButton _INIT(false); // send if updated by button or infrared remote
WLED_GLOBAL bool notifyDirect _INIT(true); // send notification if change via UI or HTTP API
WLED_GLOBAL bool notifyButton _INIT(true); // send if updated by button or infrared remote
WLED_GLOBAL bool notifyAlexa _INIT(false); // send notification if updated via Alexa
WLED_GLOBAL bool notifyHue _INIT(true); // send notification if Hue light changes
WLED_GLOBAL bool notifyHue _INIT(false); // send notification if Hue light changes
#endif
// effects
@@ -893,8 +891,6 @@ WLED_GLOBAL byte optionType;
WLED_GLOBAL bool configNeedsWrite _INIT(false); // flag to initiate saving of config
WLED_GLOBAL bool doReboot _INIT(false); // flag to initiate reboot from async handlers
WLED_GLOBAL bool psramSafe _INIT(true); // is it safe to use PSRAM (on ESP32 rev.1; compiler fix used "-mfix-esp32-psram-cache-issue")
// status led
#if defined(STATUSLED)
WLED_GLOBAL unsigned long ledStatusLastMillis _INIT(0);
@@ -968,8 +964,11 @@ WLED_GLOBAL int8_t spi_sclk _INIT(SPISCLKPIN);
// global ArduinoJson buffer
#if defined(ARDUINO_ARCH_ESP32)
WLED_GLOBAL JsonDocument *pDoc _INIT(nullptr);
WLED_GLOBAL SemaphoreHandle_t jsonBufferLockMutex _INIT(xSemaphoreCreateRecursiveMutex());
#endif
#ifdef BOARD_HAS_PSRAM
// if board has PSRAM, use it for JSON document (allocated in setup())
WLED_GLOBAL JsonDocument *pDoc _INIT(nullptr);
#else
WLED_GLOBAL StaticJsonDocument<JSON_BUFFER_SIZE> gDoc;
WLED_GLOBAL JsonDocument *pDoc _INIT(&gDoc);

View File

@@ -368,7 +368,7 @@ void initServer()
});
server.on(F("/freeheap"), HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, FPSTR(CONTENT_TYPE_PLAIN), (String)ESP.getFreeHeap());
request->send(200, FPSTR(CONTENT_TYPE_PLAIN), (String)getFreeHeapSize());
});
#ifdef WLED_ENABLE_USERMOD_PAGE

View File

@@ -124,8 +124,8 @@ void sendDataWs(AsyncWebSocketClient * client)
DEBUG_PRINTF_P(PSTR("JSON buffer size: %u for WS request (%u).\n"), pDoc->memoryUsage(), len);
// the following may no longer be necessary as heap management has been fixed by @willmmiles in AWS
size_t heap1 = ESP.getFreeHeap();
DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap());
size_t heap1 = getFreeHeapSize();
DEBUG_PRINTF_P(PSTR("heap %u\n"), getFreeHeapSize());
#ifdef ESP8266
if (len>heap1) {
DEBUG_PRINTLN(F("Out of memory (WS)!"));
@@ -134,8 +134,8 @@ void sendDataWs(AsyncWebSocketClient * client)
#endif
AsyncWebSocketBuffer buffer(len);
#ifdef ESP8266
size_t heap2 = ESP.getFreeHeap();
DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap());
size_t heap2 = getFreeHeapSize();
DEBUG_PRINTF_P(PSTR("heap %u\n"), getFreeHeapSize());
#else
size_t heap2 = 0; // ESP32 variants do not have the same issue and will work without checking heap allocation
#endif