Merge branch 'main' into multibutton

This commit is contained in:
Blaž Kristan
2025-08-04 08:04:17 +02:00
29 changed files with 856 additions and 372 deletions

View File

@@ -23,7 +23,8 @@ jobs:
uses: janheinrichmerker/action-github-changelog-generator@v2.3
with:
token: ${{ secrets.GITHUB_TOKEN }}
sinceTag: v0.15.0
sinceTag: v0.15.0
maxIssues: 500
- name: Create draft release
uses: softprops/action-gh-release@v1
with:

286
tools/wled-tools Executable file
View File

@@ -0,0 +1,286 @@
#!/bin/bash
# WLED Tools
# A utility for managing WLED devices in a local network
# https://github.com/wled/WLED
# Color Definitions
GREEN="\e[32m"
RED="\e[31m"
BLUE="\e[34m"
YELLOW="\e[33m"
RESET="\e[0m"
# Logging function
log() {
local category="$1"
local color="$2"
local text="$3"
if [ "$quiet" = true ]; then
return
fi
if [ -t 1 ]; then # Check if output is a terminal
echo -e "${color}[${category}]${RESET} ${text}"
else
echo "[${category}] ${text}"
fi
}
# Generic curl handler function
curl_handler() {
local command="$1"
local hostname="$2"
response=$($command -w "%{http_code}" -o /dev/null)
curl_exit_code=$?
if [ "$response" -ge 200 ] && [ "$response" -lt 300 ]; then
return 0
elif [ $curl_exit_code -ne 0 ]; then
log "ERROR" "$RED" "Connection error during request to $hostname (curl exit code: $curl_exit_code)."
return 1
elif [ "$response" -ge 400 ]; then
log "ERROR" "$RED" "Server error during request to $hostname (HTTP status code: $response)."
return 2
else
log "ERROR" "$RED" "Unexpected response from $hostname (HTTP status code: $response)."
return 3
fi
}
# Print help message
show_help() {
cat << EOF
Usage: wled-tools.sh [OPTIONS] COMMAND [ARGS...]
Options:
-h, --help Show this help message and exit.
-t, --target <IP/Host> Specify a single WLED device by IP address or hostname.
-D, --discover Discover multiple WLED devices using mDNS.
-d, --directory <Path> Specify a directory for saving backups (default: working directory).
-f, --firmware <File> Specify the firmware file for updating devices.
-q, --quiet Suppress logging output (also makes discover output hostnames only).
Commands:
backup Backup the current state of a WLED device or multiple discovered devices.
update Update the firmware of a WLED device or multiple discovered devices.
discover Discover WLED devices using mDNS and list their IP addresses and names.
Examples:
# Discover all WLED devices on the network
./wled-tools discover
# Backup a specific WLED device
./wled-tools -t 192.168.1.100 backup
# Backup all discovered WLED devices to a specific directory
./wled-tools -D -d /path/to/backups backup
# Update firmware on all discovered WLED devices
./wled-tools -D -f /path/to/firmware.bin update
EOF
}
# Discover devices using mDNS
discover_devices() {
if ! command -v avahi-browse &> /dev/null; then
log "ERROR" "$RED" "'avahi-browse' is required but not installed, please install avahi-utils using your preferred package manager."
exit 1
fi
# Map avahi responses to strings seperated by 0x1F (unit separator)
mapfile -t raw_devices < <(avahi-browse _wled._tcp --terminate -r -p | awk -F';' '/^=/ {print $7"\x1F"$8"\x1F"$9}')
local devices_array=()
for device in "${raw_devices[@]}"; do
IFS=$'\x1F' read -r hostname address port <<< "$device"
devices_array+=("$hostname" "$address" "$port")
done
echo "${devices_array[@]}"
}
# Backup one device
backup_one() {
local hostname="$1"
local address="$2"
local port="$3"
log "INFO" "$YELLOW" "Backing up device config/presets: $hostname ($address:$port)"
mkdir -p "$backup_dir"
local cfg_url="http://$address:$port/cfg.json"
local presets_url="http://$address:$port/presets.json"
local cfg_dest="${backup_dir}/${hostname}.cfg.json"
local presets_dest="${backup_dir}/${hostname}.presets.json"
# Write to ".tmp" files first, then move when success, to ensure we don't write partial files
local curl_command_cfg="curl -s "$cfg_url" -o "$cfg_dest.tmp""
local curl_command_presets="curl -s "$presets_url" -o "$presets_dest.tmp""
if ! curl_handler "$curl_command_cfg" "$hostname"; then
log "ERROR" "$RED" "Failed to backup configuration for $hostname"
rm -f "$cfg_dest.tmp"
return 1
fi
if ! curl_handler "$curl_command_presets" "$hostname"; then
log "ERROR" "$RED" "Failed to backup presets for $hostname"
rm -f "$presets_dest.tmp"
return 1
fi
mv "$cfg_dest.tmp" "$cfg_dest"
mv "$presets_dest.tmp" "$presets_dest"
log "INFO" "$GREEN" "Successfully backed up config and presets for $hostname"
return 0
}
# Update one device
update_one() {
local hostname="$1"
local address="$2"
local port="$3"
local firmware="$4"
log "INFO" "$YELLOW" "Starting firmware update for device: $hostname ($address:$port)"
local url="http://$address:$port/update"
local curl_command="curl -s -X POST -F "file=@$firmware" "$url""
if ! curl_handler "$curl_command" "$hostname"; then
log "ERROR" "$RED" "Failed to update firmware for $hostname"
return 1
fi
log "INFO" "$GREEN" "Successfully initiated firmware update for $hostname"
return 0
}
# Command-line arguments processing
command=""
target=""
discover=false
quiet=false
backup_dir="./"
firmware_file=""
if [ $# -eq 0 ]; then
show_help
exit 0
fi
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
show_help
exit 0
;;
-t|--target)
if [ -z "$2" ] || [[ "$2" == -* ]]; then
log "ERROR" "$RED" "The --target option requires an argument."
exit 1
fi
target="$2"
shift 2
;;
-D|--discover)
discover=true
shift
;;
-d|--directory)
if [ -z "$2" ] || [[ "$2" == -* ]]; then
log "ERROR" "$RED" "The --directory option requires an argument."
exit 1
fi
backup_dir="$2"
shift 2
;;
-f|--firmware)
if [ -z "$2" ] || [[ "$2" == -* ]]; then
log "ERROR" "$RED" "The --firmware option requires an argument."
exit 1
fi
firmware_file="$2"
shift 2
;;
-q|--quiet)
quiet=true
shift
;;
backup|update|discover)
command="$1"
shift
;;
*)
log "ERROR" "$RED" "Unknown argument: $1"
exit 1
;;
esac
done
# Execute the appropriate command
case "$command" in
discover)
read -ra devices <<< "$(discover_devices)"
for ((i=0; i<${#devices[@]}; i+=3)); do
hostname="${devices[$i]}"
address="${devices[$i+1]}"
port="${devices[$i+2]}"
if [ "$quiet" = true ]; then
echo "$hostname"
else
log "INFO" "$BLUE" "Discovered device: Hostname=$hostname, Address=$address, Port=$port"
fi
done
;;
backup)
if [ -n "$target" ]; then
# Assume target is both the hostname and address, with port 80
backup_one "$target" "$target" "80"
elif [ "$discover" = true ]; then
read -ra devices <<< "$(discover_devices)"
for ((i=0; i<${#devices[@]}; i+=3)); do
hostname="${devices[$i]}"
address="${devices[$i+1]}"
port="${devices[$i+2]}"
backup_one "$hostname" "$address" "$port"
done
else
log "ERROR" "$RED" "No target specified. Use --target or --discover."
exit 1
fi
;;
update)
# Validate firmware before proceeding
if [ -z "$firmware_file" ] || [ ! -f "$firmware_file" ]; then
log "ERROR" "$RED" "Please provide a file in --firmware that exists"
exit 1
fi
if [ -n "$target" ]; then
# Assume target is both the hostname and address, with port 80
update_one "$target" "$target" "80" "$firmware_file"
elif [ "$discover" = true ]; then
read -ra devices <<< "$(discover_devices)"
for ((i=0; i<${#devices[@]}; i+=3)); do
hostname="${devices[$i]}"
address="${devices[$i+1]}"
port="${devices[$i+2]}"
update_one "$hostname" "$address" "$port" "$firmware_file"
done
else
log "ERROR" "$RED" "No target specified. Use --target or --discover."
exit 1
fi
;;
*)
show_help
exit 1
;;
esac

View File

@@ -114,6 +114,7 @@ static um_data_t* getAudioData() {
return um_data;
}
// effect functions
/*
@@ -125,6 +126,56 @@ uint16_t mode_static(void) {
}
static const char _data_FX_MODE_STATIC[] PROGMEM = "Solid";
/*
* Copy a segment and perform (optional) color adjustments
*/
uint16_t mode_copy_segment(void) {
uint32_t sourceid = SEGMENT.custom3;
if (sourceid >= strip.getSegmentsNum() || sourceid == strip.getCurrSegmentId()) { // invalid source
SEGMENT.fadeToBlackBy(5); // fade out
return FRAMETIME;
}
Segment sourcesegment = strip.getSegment(sourceid);
if (sourcesegment.isActive()) {
uint32_t sourcecolor;
uint32_t destcolor;
if(sourcesegment.is2D()) { // 2D source, note: 2D to 1D just copies the first row (or first column if 'Switch axis' is checked in FX)
for (unsigned y = 0; y < SEGMENT.vHeight(); y++) {
for (unsigned x = 0; x < SEGMENT.vWidth(); x++) {
unsigned sx = x; // source coordinates
unsigned sy = y;
if(SEGMENT.check1) std::swap(sx, sy); // flip axis
if(SEGMENT.check2) {
sourcecolor = strip.getPixelColorXY(sx + sourcesegment.start, sy + sourcesegment.startY); // read from global buffer (reads the last rendered frame)
}
else {
sourcesegment.setDrawDimensions(); // set to source segment dimensions
sourcecolor = sourcesegment.getPixelColorXY(sx, sy); // read from segment buffer
}
destcolor = adjust_color(sourcecolor, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2);
SEGMENT.setDrawDimensions(); // reset to current segment dimensions
SEGMENT.setPixelColorXY(x, y, destcolor);
}
}
} else { // 1D source, source can be expanded into 2D
for (unsigned i = 0; i < SEGMENT.vLength(); i++) {
if(SEGMENT.check2) {
sourcecolor = strip.getPixelColor(i + sourcesegment.start); // read from global buffer (reads the last rendered frame)
}
else {
sourcesegment.setDrawDimensions(); // set to source segment dimensions
sourcecolor = sourcesegment.getPixelColor(i);
}
destcolor = adjust_color(sourcecolor, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2);
SEGMENT.setDrawDimensions(); // reset to current segment dimensions
SEGMENT.setPixelColor(i, destcolor);
}
}
}
return FRAMETIME;
}
static const char _data_FX_MODE_COPY[] PROGMEM = "Copy Segment@,Color shift,Lighten,Brighten,ID,Axis(2D),FullStack(last frame);;;12;ix=0,c1=0,c2=0,c3=0";
/*
* Blink/strobe function
@@ -4715,30 +4766,17 @@ class AuroraWave {
};
uint16_t mode_aurora(void) {
//aux1 = Wavecount
//aux2 = Intensity in last loop
AuroraWave* waves;
SEGENV.aux1 = map(SEGMENT.intensity, 0, 255, 2, W_MAX_COUNT); // aux1 = Wavecount
if(!SEGENV.allocateData(sizeof(AuroraWave) * SEGENV.aux1)) { // 20 on ESP32, 9 on ESP8266
return mode_static(); //allocation failed
}
waves = reinterpret_cast<AuroraWave*>(SEGENV.data);
//TODO: I am not sure this is a correct way of handling memory allocation since if it fails on 1st run
// it will display static effect but on second run it may crash ESP since data will be nullptr
if(SEGENV.aux0 != SEGMENT.intensity || SEGENV.call == 0) {
//Intensity slider changed or first call
SEGENV.aux1 = map(SEGMENT.intensity, 0, 255, 2, W_MAX_COUNT);
SEGENV.aux0 = SEGMENT.intensity;
if(!SEGENV.allocateData(sizeof(AuroraWave) * SEGENV.aux1)) { // 26 on 32 segment ESP32, 9 on 16 segment ESP8266
return mode_static(); //allocation failed
}
waves = reinterpret_cast<AuroraWave*>(SEGENV.data);
if(SEGENV.call == 0) {
for (int i = 0; i < SEGENV.aux1; i++) {
waves[i].init(SEGLEN, CRGB(SEGMENT.color_from_palette(hw_random8(), false, false, hw_random8(0, 3))));
}
} else {
waves = reinterpret_cast<AuroraWave*>(SEGENV.data);
}
for (int i = 0; i < SEGENV.aux1; i++) {
@@ -8444,7 +8482,6 @@ static const char _data_FX_MODE_PARTICLEPERLIN[] PROGMEM = "PS Fuzzy Noise@Speed
#define NUMBEROFSOURCES 8
uint16_t mode_particleimpact(void) {
ParticleSystem2D *PartSys = nullptr;
uint32_t i = 0;
uint32_t numMeteors;
PSsettings2D meteorsettings;
meteorsettings.asByte = 0b00101000; // PS settings for meteors: bounceY and gravity enabled
@@ -8457,7 +8494,7 @@ uint16_t mode_particleimpact(void) {
PartSys->setBounceY(true); // always use ground bounce
PartSys->setWallRoughness(220); // high roughness
numMeteors = min(PartSys->numSources, (uint32_t)NUMBEROFSOURCES);
for (i = 0; i < numMeteors; i++) {
for (uint32_t i = 0; i < numMeteors; i++) {
PartSys->sources[i].source.ttl = hw_random16(10 * i); // set initial delay for meteors
PartSys->sources[i].source.vy = 10; // at positive speeds, no particles are emitted and if particle dies, it will be relaunched
}
@@ -8479,7 +8516,7 @@ uint16_t mode_particleimpact(void) {
numMeteors = min(PartSys->numSources, (uint32_t)NUMBEROFSOURCES);
uint32_t emitparticles; // number of particles to emit for each rocket's state
for (i = 0; i < numMeteors; i++) {
for (uint32_t i = 0; i < numMeteors; i++) {
// determine meteor state by its speed:
if ( PartSys->sources[i].source.vy < 0) // moving down, emit sparks
emitparticles = 1;
@@ -8495,7 +8532,7 @@ uint16_t mode_particleimpact(void) {
}
// update the meteors, set the speed state
for (i = 0; i < numMeteors; i++) {
for (uint32_t i = 0; i < numMeteors; i++) {
if (PartSys->sources[i].source.ttl) {
PartSys->sources[i].source.ttl--; // note: this saves an if statement, but moving down particles age twice
if (PartSys->sources[i].source.vy < 0) { // move down
@@ -8786,7 +8823,7 @@ uint16_t mode_particleGEQ(void) {
//set particle properties TODO: could also use the spray...
PartSys->particles[i].ttl = 20 + map(SEGMENT.intensity, 0,255, emitspeed>>1, emitspeed + hw_random16(emitspeed)) ; // set particle alive, particle lifespan is in number of frames
PartSys->particles[i].x = xposition + hw_random16(binwidth) - (binwidth>>1); // position randomly, deviating half a bin width
PartSys->particles[i].y = PS_P_RADIUS; // start at the bottom (PS_P_RADIUS is minimum position a particle is fully in frame)
PartSys->particles[i].y = 0; // start at the bottom
PartSys->particles[i].vx = hw_random16(SEGMENT.custom1>>1)-(SEGMENT.custom1>>2) ; //x-speed variation: +/- custom1/4
PartSys->particles[i].vy = emitspeed;
PartSys->particles[i].hue = (bin<<4) + hw_random16(17) - 8; // color from palette according to bin
@@ -10617,6 +10654,7 @@ void WS2812FX::setupEffectData() {
_modeData.push_back(_data_RESERVED);
}
// now replace all pre-allocated effects
addEffect(FX_MODE_COPY, &mode_copy_segment, _data_FX_MODE_COPY);
// --- 1D non-audio effects ---
addEffect(FX_MODE_BLINK, &mode_blink, _data_FX_MODE_BLINK);
addEffect(FX_MODE_BREATH, &mode_breath, _data_FX_MODE_BREATH);

11
wled00/FX.h Executable file → Normal file
View File

@@ -228,6 +228,7 @@ extern byte realtimeMode; // used in getMappedPixelIndex()
#define FX_MODE_LAKE 75
#define FX_MODE_METEOR 76
//#define FX_MODE_METEOR_SMOOTH 77 // replaced by Meteor
#define FX_MODE_COPY 77
#define FX_MODE_RAILWAY 78
#define FX_MODE_RIPPLE 79
#define FX_MODE_TWINKLEFOX 80
@@ -685,6 +686,7 @@ class Segment {
// 1D strip
uint16_t virtualLength() const;
uint16_t maxMappingLength() const;
[[gnu::hot]] void setPixelColor(int n, uint32_t c) const; // set relative pixel within segment with color
inline void setPixelColor(unsigned n, uint32_t c) const { setPixelColor(int(n), c); }
inline void setPixelColor(int n, byte r, byte g, byte b, byte w = 0) const { setPixelColor(n, RGBW32(r,g,b,w)); }
@@ -723,6 +725,13 @@ class Segment {
return 1;
#endif
}
inline unsigned rawLength() const { // returns length of used raw pixel buffer (eg. get/setPixelColorRaw())
#ifndef WLED_DISABLE_2D
if (is2D()) return virtualWidth() * virtualHeight();
#endif
return virtualLength();
}
#ifndef WLED_DISABLE_2D
inline bool is2D() const { return (width()>1 && height()>1); }
[[gnu::hot]] void setPixelColorXY(int x, int y, uint32_t c) const; // set relative pixel within segment with color
@@ -800,6 +809,8 @@ class Segment {
inline void wu_pixel(uint32_t x, uint32_t y, CRGB c) {}
#endif
friend class WS2812FX;
friend class ParticleSystem2D;
friend class ParticleSystem1D;
};
// main "strip" class (108 bytes)

View File

@@ -159,8 +159,16 @@ bool Segment::allocateData(size_t len) {
return false;
}
// prefer DRAM over SPI RAM on ESP32 since it is slow
if (data) data = (byte*)d_realloc(data, len);
else data = (byte*)d_malloc(len);
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);
if (data) {
memset(data, 0, len); // erase buffer
Segment::addUsedSegmentData(len - _dataLen);
@@ -170,7 +178,6 @@ bool Segment::allocateData(size_t len) {
}
// allocation failed
DEBUG_PRINTLN(F("!!! Allocation failed. !!!"));
Segment::addUsedSegmentData(-_dataLen); // subtract original buffer size
errorFlag = ERR_NORAM;
return false;
}
@@ -449,8 +456,8 @@ void Segment::setGeometry(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, ui
}
// re-allocate FX render buffer
if (length() != oldLength) {
if (pixels) pixels = static_cast<uint32_t*>(d_realloc(pixels, sizeof(uint32_t) * length()));
else pixels = static_cast<uint32_t*>(d_malloc(sizeof(uint32_t) * length()));
if (pixels) d_free(pixels); // using realloc on large buffers can cause additional fragmentation instead of reducing it
pixels = static_cast<uint32_t*>(d_malloc(sizeof(uint32_t) * length()));
if (!pixels) {
DEBUG_PRINTLN(F("!!! Not enough RAM for pixel buffer !!!"));
errorFlag = ERR_NORAM_PX;
@@ -563,8 +570,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) name = static_cast<char*>(d_realloc(name, newLen+1));
else name = static_cast<char*>(d_malloc(newLen+1));
if (name) d_free(name); // free old name
name = static_cast<char*>(d_malloc(newLen+1));
if (name) strlcpy(name, newName, newLen+1);
name[newLen] = 0;
return *this;
@@ -645,6 +652,14 @@ uint16_t Segment::virtualLength() const {
return vLength;
}
#ifndef WLED_DISABLE_2D
// maximum length of a mapped 1D segment, used in PS for buffer allocation
uint16_t Segment::maxMappingLength() const {
uint32_t vW = virtualWidth();
uint32_t vH = virtualHeight();
return max(sqrt32_bw(vH*vH + vW*vW), (uint32_t)getPinwheelLength(vW, vH)); // use diagonal
}
#endif
// pixel is clipped if it falls outside clipping range
// if clipping start > stop the clipping range is inverted
bool IRAM_ATTR_YN Segment::isPixelClipped(int i) const {
@@ -729,8 +744,8 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) const
}
break;
case M12_pCorner:
for (int x = 0; x <= i; x++) setPixelColorRaw(XY(x, i), col);
for (int y = 0; y < i; y++) setPixelColorRaw(XY(i, y), col);
for (int x = 0; x <= i; x++) setPixelColorXY(x, i, col); // note: <= to include i=0. Relies on overflow check in sPC()
for (int y = 0; y < i; y++) setPixelColorXY(i, y, col);
break;
case M12_sPinwheel: {
// Uses Bresenham's algorithm to place coordinates of two lines in arrays then draws between them
@@ -987,7 +1002,8 @@ void Segment::fade_out(uint8_t rate) const {
if (!isActive()) return; // not active
rate = (256-rate) >> 1;
const int mappedRate = 256 / (rate + 1);
for (unsigned j = 0; j < vLength(); j++) {
const size_t rlength = rawLength(); // calculate only once
for (unsigned j = 0; j < rlength; j++) {
uint32_t color = getPixelColorRaw(j);
if (color == colors[1]) continue; // already at target color
for (int i = 0; i < 32; i += 8) {
@@ -1008,13 +1024,15 @@ void Segment::fade_out(uint8_t rate) const {
// fades all pixels to secondary color
void Segment::fadeToSecondaryBy(uint8_t fadeBy) const {
if (!isActive() || fadeBy == 0) return; // optimization - no scaling to apply
for (unsigned i = 0; i < vLength(); i++) setPixelColorRaw(i, color_blend(getPixelColorRaw(i), colors[1], fadeBy));
const size_t rlength = rawLength(); // calculate only once
for (unsigned i = 0; i < rlength; i++) setPixelColorRaw(i, color_blend(getPixelColorRaw(i), colors[1], fadeBy));
}
// fades all pixels to black using nscale8()
void Segment::fadeToBlackBy(uint8_t fadeBy) const {
if (!isActive() || fadeBy == 0) return; // optimization - no scaling to apply
for (unsigned i = 0; i < vLength(); i++) setPixelColorRaw(i, color_fade(getPixelColorRaw(i), 255-fadeBy));
const size_t rlength = rawLength(); // calculate only once
for (unsigned i = 0; i < rlength; i++) setPixelColorRaw(i, color_fade(getPixelColorRaw(i), 255-fadeBy));
}
/*
@@ -1136,21 +1154,27 @@ void WS2812FX::finalizeInit() {
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
// determine if it is sensible to use parallel I2S outputs on ESP32 (i.e. more than 5 outputs = 1 I2S + 4 RMT)
unsigned maxLedsOnBus = 0;
unsigned busType = 0;
for (const auto &bus : busConfigs) {
if (Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type)) {
digitalCount++;
if (busType == 0) busType = bus.type; // remember first bus type
if (busType != bus.type) {
DEBUG_PRINTF_P(PSTR("Mixed digital bus types detected! Forcing single I2S output.\n"));
useParallelI2S = false; // mixed bus types, no parallel I2S
}
if (bus.count > maxLedsOnBus) maxLedsOnBus = bus.count;
}
}
DEBUG_PRINTF_P(PSTR("Maximum LEDs on a bus: %u\nDigital buses: %u\n"), maxLedsOnBus, digitalCount);
// we may remove 300 LEDs per bus limit when NeoPixelBus is updated beyond 2.9.0
if (maxLedsOnBus <= 300 && useParallelI2S) BusManager::useParallelOutput(); // must call before creating buses
// we may remove 600 LEDs per bus limit when NeoPixelBus is updated beyond 2.8.3
if (maxLedsOnBus <= 600 && useParallelI2S) BusManager::useParallelOutput(); // must call before creating buses
else useParallelI2S = false; // enforce single I2S
digitalCount = 0;
#endif
// create buses/outputs
unsigned mem = 0;
digitalCount = 0;
for (const auto &bus : busConfigs) {
mem += bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount++ : 0); // includes global buffer
if (mem <= MAX_LED_MEMORY) {
@@ -1186,8 +1210,8 @@ 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!)
if (_pixels) _pixels = static_cast<uint32_t*>(d_realloc(_pixels, getLengthTotal() * sizeof(uint32_t)));
else _pixels = static_cast<uint32_t*>(d_malloc(getLengthTotal() * sizeof(uint32_t)));
if (_pixels) d_free(_pixels); // using realloc on large buffers can cause additional fragmentation instead of reducing it
_pixels = static_cast<uint32_t*>(d_malloc(getLengthTotal() * sizeof(uint32_t)));
DEBUG_PRINTF_P(PSTR("strip buffer size: %uB\n"), getLengthTotal() * sizeof(uint32_t));
DEBUG_PRINTF_P(PSTR("Heap after strip init: %uB\n"), ESP.getFreeHeap());
@@ -1320,6 +1344,11 @@ void WS2812FX::blendSegment(const Segment &topSegment) const {
uint8_t opacity = topSegment.currentBri(); // returns transitioned opacity for style FADE
uint8_t cct = topSegment.currentCCT();
if (length == 1) {
// Can't blend only a single pixel, prevents crash when bus init fails
return;
}
Segment::setClippingRect(0, 0); // disable clipping by default
const unsigned dw = (blendingStyle==BLEND_STYLE_OUTSIDE_IN ? progInv : progress) * width / 0xFFFFU + 1;
@@ -1674,7 +1703,7 @@ void WS2812FX::setTransitionMode(bool t) {
// wait until frame is over (service() has finished or time for 1 frame has passed; yield() crashes on 8266)
void WS2812FX::waitForIt() {
unsigned long maxWait = millis() + getFrameTime();
unsigned long maxWait = millis() + getFrameTime() + 100; // TODO: this needs a proper fix for timeout!
while (isServicing() && maxWait > millis()) delay(1);
#ifdef WLED_DEBUG
if (millis() >= maxWait) DEBUG_PRINTLN(F("Waited for strip to finish servicing."));

View File

@@ -17,9 +17,8 @@
// local shared functions (used both in 1D and 2D system)
static int32_t calcForce_dv(const int8_t force, uint8_t &counter);
static bool checkBoundsAndWrap(int32_t &position, const int32_t max, const int32_t particleradius, const bool wrap); // returns false if out of bounds by more than particleradius
static void fast_color_add(CRGB &c1, const CRGB &c2, uint8_t scale = 255); // fast and accurate color adding with scaling (scales c2 before adding)
static void fast_color_scale(CRGB &c, const uint8_t scale); // fast scaling function using 32bit variable and pointer. note: keep 'scale' within 0-255
//static CRGB *allocateCRGBbuffer(uint32_t length);
static uint32_t fast_color_add(CRGBW c1, const CRGBW c2, uint8_t scale = 255); // fast and accurate color adding with scaling (scales c2 before adding)
static uint32_t fast_color_scale(CRGBW c, const uint8_t scale); // fast scaling function using 32bit variable and pointer. note: keep 'scale' within 0-255
#endif
#ifndef WLED_DISABLE_PARTICLESYSTEM2D
@@ -558,7 +557,11 @@ void ParticleSystem2D::pointAttractor(const uint32_t particleindex, PSparticle &
// warning: do not render out of bounds particles or system will crash! rendering does not check if particle is out of bounds
// firemode is only used for PS Fire FX
void ParticleSystem2D::render() {
CRGB baseRGB;
if(framebuffer == nullptr) {
PSPRINTLN(F("PS render: no framebuffer!"));
return;
}
CRGBW baseRGB;
uint32_t brightness; // particle brightness, fades if dying
TBlendType blend = LINEARBLEND; // default color rendering: wrap palette
if (particlesettings.colorByAge) {
@@ -569,13 +572,13 @@ void ParticleSystem2D::render() {
for (int32_t y = 0; y <= maxYpixel; y++) {
int index = y * (maxXpixel + 1);
for (int32_t x = 0; x <= maxXpixel; x++) {
fast_color_scale(framebuffer[index], motionBlur); // note: could skip if only globalsmear is active but usually they are both active and scaling is fast enough
framebuffer[index] = fast_color_scale(framebuffer[index], motionBlur); // note: could skip if only globalsmear is active but usually they are both active and scaling is fast enough
index++;
}
}
}
else { // no blurring: clear buffer
memset(framebuffer, 0, (maxXpixel+1) * (maxYpixel+1) * sizeof(CRGB));
memset(framebuffer, 0, (maxXpixel+1) * (maxYpixel+1) * sizeof(CRGBW));
}
// go over particles and render them to the buffer
@@ -593,14 +596,12 @@ void ParticleSystem2D::render() {
baseRGB = ColorFromPaletteWLED(SEGPALETTE, particles[i].hue, 255, blend);
if (particles[i].sat < 255) {
CHSV32 baseHSV;
rgb2hsv((uint32_t((byte(baseRGB.r) << 16) | (byte(baseRGB.g) << 8) | (byte(baseRGB.b)))), baseHSV); // convert to HSV
rgb2hsv(baseRGB.color32, baseHSV); // convert to HSV
baseHSV.s = min(baseHSV.s, particles[i].sat); // set the saturation but don't increase it
uint32_t tempcolor;
hsv2rgb(baseHSV, tempcolor); // convert back to RGB
baseRGB = (CRGB)tempcolor;
hsv2rgb(baseHSV, baseRGB.color32); // convert back to RGB
}
}
brightness = gamma8(brightness); // apply gamma correction, used for gamma-inverted brightness distribution
if(gammaCorrectCol) brightness = gamma8(brightness); // apply gamma correction, used for gamma-inverted brightness distribution
renderParticle(i, brightness, baseRGB, particlesettings.wrapX, particlesettings.wrapY);
}
@@ -621,18 +622,10 @@ void ParticleSystem2D::render() {
if (smearBlur) {
blur2D(framebuffer, maxXpixel + 1, maxYpixel + 1, smearBlur, smearBlur);
}
// transfer the framebuffer to the segment
for (int y = 0; y <= maxYpixel; y++) {
int index = y * (maxXpixel + 1); // current row index for 1D buffer
for (int x = 0; x <= maxXpixel; x++) {
SEGMENT.setPixelColorXY(x, y, framebuffer[index++]);
}
}
}
// calculate pixel positions and brightness distribution and render the particle to local buffer or global buffer
__attribute__((optimize("O2"))) void ParticleSystem2D::renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGB& color, const bool wrapX, const bool wrapY) {
__attribute__((optimize("O2"))) void ParticleSystem2D::renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGBW& color, const bool wrapX, const bool wrapY) {
uint32_t size = particlesize;
if (advPartProps && advPartProps[particleindex].size > 0) // use advanced size properties (0 means use global size including single pixel rendering)
size = advPartProps[particleindex].size;
@@ -641,7 +634,8 @@ __attribute__((optimize("O2"))) void ParticleSystem2D::renderParticle(const uint
uint32_t x = particles[particleindex].x >> PS_P_RADIUS_SHIFT;
uint32_t y = particles[particleindex].y >> PS_P_RADIUS_SHIFT;
if (x <= (uint32_t)maxXpixel && y <= (uint32_t)maxYpixel) {
fast_color_add(framebuffer[x + (maxYpixel - y) * (maxXpixel + 1)], color, brightness);
uint32_t index = x + (maxYpixel - y) * (maxXpixel + 1); // flip y coordinate (0,0 is bottom left in PS but top left in framebuffer)
framebuffer[index] = fast_color_add(framebuffer[index], color, brightness);
}
return;
}
@@ -681,20 +675,22 @@ __attribute__((optimize("O2"))) void ParticleSystem2D::renderParticle(const uint
// - scale brigthness with gamma correction (done in render())
// - apply inverse gamma correction to brightness values
// - gamma is applied again in show() -> the resulting brightness distribution is linear but gamma corrected in total
pxlbrightness[0] = gamma8inv(pxlbrightness[0]); // use look-up-table for invers gamma
pxlbrightness[1] = gamma8inv(pxlbrightness[1]);
pxlbrightness[2] = gamma8inv(pxlbrightness[2]);
pxlbrightness[3] = gamma8inv(pxlbrightness[3]);
if(gammaCorrectCol) {
pxlbrightness[0] = gamma8inv(pxlbrightness[0]); // use look-up-table for invers gamma
pxlbrightness[1] = gamma8inv(pxlbrightness[1]);
pxlbrightness[2] = gamma8inv(pxlbrightness[2]);
pxlbrightness[3] = gamma8inv(pxlbrightness[3]);
}
if (advPartProps && advPartProps[particleindex].size > 1) { //render particle to a bigger size
CRGB renderbuffer[100]; // 10x10 pixel buffer
uint32_t renderbuffer[100]; // 10x10 pixel buffer
memset(renderbuffer, 0, sizeof(renderbuffer)); // clear buffer
//particle size to pixels: < 64 is 4x4, < 128 is 6x6, < 192 is 8x8, bigger is 10x10
//first, render the pixel to the center of the renderbuffer, then apply 2D blurring
fast_color_add(renderbuffer[4 + (4 * 10)], color, pxlbrightness[0]); // oCrder is: bottom left, bottom right, top right, top left
fast_color_add(renderbuffer[5 + (4 * 10)], color, pxlbrightness[1]);
fast_color_add(renderbuffer[5 + (5 * 10)], color, pxlbrightness[2]);
fast_color_add(renderbuffer[4 + (5 * 10)], color, pxlbrightness[3]);
renderbuffer[4 + (4 * 10)] = fast_color_add(renderbuffer[4 + (4 * 10)], color, pxlbrightness[0]); // order is: bottom left, bottom right, top right, top left
renderbuffer[5 + (4 * 10)] = fast_color_add(renderbuffer[5 + (4 * 10)], color, pxlbrightness[1]);
renderbuffer[5 + (5 * 10)] = fast_color_add(renderbuffer[5 + (5 * 10)], color, pxlbrightness[2]);
renderbuffer[4 + (5 * 10)] = fast_color_add(renderbuffer[4 + (5 * 10)], color, pxlbrightness[3]);
uint32_t rendersize = 2; // initialize render size, minimum is 4x4 pixels, it is incremented int he loop below to start with 4
uint32_t offset = 4; // offset to zero coordinate to write/read data in renderbuffer (actually needs to be 3, is decremented in the loop below)
uint32_t maxsize = advPartProps[particleindex].size;
@@ -751,7 +747,8 @@ __attribute__((optimize("O2"))) void ParticleSystem2D::renderParticle(const uint
else
continue;
}
fast_color_add(framebuffer[xfb + (maxYpixel - yfb) * (maxXpixel + 1)], renderbuffer[xrb + yrb * 10]);
uint32_t idx = xfb + (maxYpixel - yfb) * (maxXpixel + 1); // flip y coordinate (0,0 is bottom left in PS but top left in framebuffer)
framebuffer[idx] = fast_color_add(framebuffer[idx], renderbuffer[xrb + yrb * 10]);
}
}
} else { // standard rendering (2x2 pixels)
@@ -786,8 +783,10 @@ __attribute__((optimize("O2"))) void ParticleSystem2D::renderParticle(const uint
}
}
for (uint32_t i = 0; i < 4; i++) {
if (pixelvalid[i])
fast_color_add(framebuffer[pixco[i].x + (maxYpixel - pixco[i].y) * (maxXpixel + 1)], color, pxlbrightness[i]); // order is: bottom left, bottom right, top right, top left
if (pixelvalid[i]) {
uint32_t idx = pixco[i].x + (maxYpixel - pixco[i].y) * (maxXpixel + 1); // flip y coordinate (0,0 is bottom left in PS but top left in framebuffer)
framebuffer[idx] = fast_color_add(framebuffer[idx], color, pxlbrightness[i]); // order is: bottom left, bottom right, top right, top left
}
}
}
}
@@ -970,17 +969,17 @@ __attribute__((optimize("O2"))) void ParticleSystem2D::collideParticles(PSpartic
// update size and pointers (memory location and size can change dynamically)
// note: do not access the PS class in FX befor running this function (or it messes up SEGENV.data)
void ParticleSystem2D::updateSystem(void) {
PSPRINTLN("updateSystem2D");
//PSPRINTLN("updateSystem2D");
setMatrixSize(SEGMENT.vWidth(), SEGMENT.vHeight());
updatePSpointers(advPartProps != nullptr, advPartSize != nullptr); // update pointers to PS data, also updates availableParticles
PSPRINTLN("\n END update System2D, running FX...");
//PSPRINTLN("\n END update System2D, running FX...");
}
// set the pointers for the class (this only has to be done once and not on every FX call, only the class pointer needs to be reassigned to SEGENV.data every time)
// function returns the pointer to the next byte available for the FX (if it assigned more memory for other stuff using the above allocate function)
// FX handles the PSsources, need to tell this function how many there are
void ParticleSystem2D::updatePSpointers(bool isadvanced, bool sizecontrol) {
PSPRINTLN("updatePSpointers");
//PSPRINTLN("updatePSpointers");
// Note on memory alignment:
// a pointer MUST be 4 byte aligned. sizeof() in a struct/class is always aligned to the largest element. if it contains a 32bit, it will be padded to 4 bytes, 16bit is padded to 2byte alignment.
// The PS is aligned to 4 bytes, a PSparticle is aligned to 2 and a struct containing only byte sized variables is not aligned at all and may need to be padded when dividing the memoryblock.
@@ -988,11 +987,8 @@ void ParticleSystem2D::updatePSpointers(bool isadvanced, bool sizecontrol) {
particles = reinterpret_cast<PSparticle *>(this + 1); // pointer to particles
particleFlags = reinterpret_cast<PSparticleFlags *>(particles + numParticles); // pointer to particle flags
sources = reinterpret_cast<PSsource *>(particleFlags + numParticles); // pointer to source(s) at data+sizeof(ParticleSystem2D)
framebuffer = reinterpret_cast<CRGB *>(sources + numSources); // pointer to framebuffer
// align pointer after framebuffer
uintptr_t p = reinterpret_cast<uintptr_t>(framebuffer + (maxXpixel+1)*(maxYpixel+1));
p = (p + 3) & ~0x03; // align to 4-byte boundary
PSdataEnd = reinterpret_cast<uint8_t *>(p); // pointer to first available byte after the PS for FX additional data
framebuffer = SEGMENT.getPixels(); // pointer to framebuffer
PSdataEnd = reinterpret_cast<uint8_t *>(sources + numSources); // pointer to first available byte after the PS for FX additional data (already aligned to 4 byte boundary)
if (isadvanced) {
advPartProps = reinterpret_cast<PSadvancedParticle *>(PSdataEnd);
PSdataEnd = reinterpret_cast<uint8_t *>(advPartProps + numParticles);
@@ -1015,8 +1011,8 @@ void ParticleSystem2D::updatePSpointers(bool isadvanced, bool sizecontrol) {
// for speed, 1D array and 32bit variables are used, make sure to limit them to 8bit (0-255) or result is undefined
// to blur a subset of the buffer, change the xsize/ysize and set xstart/ystart to the desired starting coordinates (default start is 0/0)
// subset blurring only works on 10x10 buffer (single particle rendering), if other sizes are needed, buffer width must be passed as parameter
void blur2D(CRGB *colorbuffer, uint32_t xsize, uint32_t ysize, uint32_t xblur, uint32_t yblur, uint32_t xstart, uint32_t ystart, bool isparticle) {
CRGB seeppart, carryover;
void blur2D(uint32_t *colorbuffer, uint32_t xsize, uint32_t ysize, uint32_t xblur, uint32_t yblur, uint32_t xstart, uint32_t ystart, bool isparticle) {
CRGBW seeppart, carryover;
uint32_t seep = xblur >> 1;
uint32_t width = xsize; // width of the buffer, used to calculate the index of the pixel
@@ -1030,12 +1026,11 @@ void blur2D(CRGB *colorbuffer, uint32_t xsize, uint32_t ysize, uint32_t xblur, u
carryover = BLACK;
uint32_t indexXY = xstart + y * width;
for (uint32_t x = xstart; x < xstart + xsize; x++) {
seeppart = colorbuffer[indexXY]; // create copy of current color
fast_color_scale(seeppart, seep); // scale it and seep to neighbours
seeppart = fast_color_scale(colorbuffer[indexXY], seep); // scale it and seep to neighbours
if (x > 0) {
fast_color_add(colorbuffer[indexXY - 1], seeppart);
if (carryover) // note: check adds overhead but is faster on average
fast_color_add(colorbuffer[indexXY], carryover);
colorbuffer[indexXY - 1] = fast_color_add(colorbuffer[indexXY - 1], seeppart);
if (carryover.color32) // note: check adds overhead but is faster on average
colorbuffer[indexXY] = fast_color_add(colorbuffer[indexXY], carryover);
}
carryover = seeppart;
indexXY++; // next pixel in x direction
@@ -1052,12 +1047,11 @@ void blur2D(CRGB *colorbuffer, uint32_t xsize, uint32_t ysize, uint32_t xblur, u
carryover = BLACK;
uint32_t indexXY = x + ystart * width;
for (uint32_t y = ystart; y < ystart + ysize; y++) {
seeppart = colorbuffer[indexXY]; // create copy of current color
fast_color_scale(seeppart, seep); // scale it and seep to neighbours
seeppart = fast_color_scale(colorbuffer[indexXY], seep); // scale it and seep to neighbours
if (y > 0) {
fast_color_add(colorbuffer[indexXY - width], seeppart);
if (carryover) // note: check adds overhead but is faster on average
fast_color_add(colorbuffer[indexXY], carryover);
colorbuffer[indexXY - width] = fast_color_add(colorbuffer[indexXY - width], seeppart);
if (carryover.color32) // note: check adds overhead but is faster on average
colorbuffer[indexXY] = fast_color_add(colorbuffer[indexXY], carryover);
}
carryover = seeppart;
indexXY += width; // next pixel in y direction
@@ -1068,13 +1062,7 @@ void blur2D(CRGB *colorbuffer, uint32_t xsize, uint32_t ysize, uint32_t xblur, u
//non class functions to use for initialization
uint32_t calculateNumberOfParticles2D(uint32_t const pixels, const bool isadvanced, const bool sizecontrol) {
uint32_t numberofParticles = pixels; // 1 particle per pixel (for example 512 particles on 32x16)
#ifdef ESP8266
uint32_t particlelimit = ESP8266_MAXPARTICLES; // maximum number of paticles allowed (based on one segment of 16x16 and 4k effect ram)
#elif ARDUINO_ARCH_ESP32S2
uint32_t particlelimit = ESP32S2_MAXPARTICLES; // maximum number of paticles allowed (based on one segment of 32x32 and 24k effect ram)
#else
uint32_t particlelimit = ESP32_MAXPARTICLES; // maximum number of paticles allowed (based on two segments of 32x32 and 40k effect ram)
#endif
uint32_t particlelimit = MAXPARTICLES_2D; // maximum number of paticles allowed
numberofParticles = max((uint32_t)4, min(numberofParticles, particlelimit)); // limit to 4 - particlelimit
if (isadvanced) // advanced property array needs ram, reduce number of particles to use the same amount
numberofParticles = (numberofParticles * sizeof(PSparticle)) / (sizeof(PSparticle) + sizeof(PSadvancedParticle));
@@ -1087,16 +1075,8 @@ uint32_t calculateNumberOfParticles2D(uint32_t const pixels, const bool isadvanc
}
uint32_t calculateNumberOfSources2D(uint32_t pixels, uint32_t requestedsources) {
#ifdef ESP8266
int numberofSources = min((pixels) / 8, (uint32_t)requestedsources);
numberofSources = max(1, min(numberofSources, ESP8266_MAXSOURCES)); // limit
#elif ARDUINO_ARCH_ESP32S2
int numberofSources = min((pixels) / 6, (uint32_t)requestedsources);
numberofSources = max(1, min(numberofSources, ESP32S2_MAXSOURCES)); // limit
#else
int numberofSources = min((pixels) / 4, (uint32_t)requestedsources);
numberofSources = max(1, min(numberofSources, ESP32_MAXSOURCES)); // limit
#endif
int numberofSources = min((pixels) / SOURCEREDUCTIONFACTOR, (uint32_t)requestedsources);
numberofSources = max(1, min(numberofSources, MAXSOURCES_2D)); // limit
// make sure it is a multiple of 4 for proper memory alignment
numberofSources = (numberofSources+3) & ~0x03;
return numberofSources;
@@ -1115,10 +1095,7 @@ bool allocateParticleSystemMemory2D(uint32_t numparticles, uint32_t numsources,
if (sizecontrol)
requiredmemory += sizeof(PSsizeControl) * numparticles;
requiredmemory += sizeof(PSsource) * numsources;
requiredmemory += sizeof(CRGB) * SEGMENT.virtualLength(); // virtualLength is witdh * height
requiredmemory += additionalbytes + 3; // add 3 to ensure there is room for stuffing bytes
//requiredmemory = (requiredmemory + 3) & ~0x03; // align memory block to next 4-byte boundary
PSPRINTLN("mem alloc: " + String(requiredmemory));
requiredmemory += additionalbytes;
return(SEGMENT.allocateData(requiredmemory));
}
@@ -1132,17 +1109,26 @@ bool initParticleSystem2D(ParticleSystem2D *&PartSys, uint32_t requestedsources,
uint32_t numparticles = calculateNumberOfParticles2D(pixels, advanced, sizecontrol);
PSPRINT(" segmentsize:" + String(cols) + " x " + String(rows));
PSPRINT(" request numparticles:" + String(numparticles));
PSPRINTLN(" request numparticles:" + String(numparticles));
uint32_t numsources = calculateNumberOfSources2D(pixels, requestedsources);
if (!allocateParticleSystemMemory2D(numparticles, numsources, advanced, sizecontrol, additionalbytes))
{
DEBUG_PRINT(F("PS init failed: memory depleted"));
return false;
bool allocsuccess = false;
while(numparticles >= 4) { // make sure we have at least 4 particles or quit
if (allocateParticleSystemMemory2D(numparticles, numsources, advanced, sizecontrol, additionalbytes)) {
PSPRINTLN(F("PS 2D alloc succeeded"));
allocsuccess = true;
break; // allocation succeeded
}
numparticles /= 2; // cut number of particles in half and try again
PSPRINTLN(F("PS 2D alloc failed, trying with less particles..."));
}
if (!allocsuccess) {
PSPRINTLN(F("PS 2D alloc failed, not enough memory!"));
return false; // allocation failed
}
PartSys = new (SEGENV.data) ParticleSystem2D(cols, rows, numparticles, numsources, advanced, sizecontrol); // particle system constructor
PSPRINTLN("******init done, pointers:");
PSPRINTLN(F("2D PS init done"));
return true;
}
@@ -1435,28 +1421,26 @@ void ParticleSystem1D::applyFriction(int32_t coefficient) {
// if wrap is set, particles half out of bounds are rendered to the other side of the matrix
// warning: do not render out of bounds particles or system will crash! rendering does not check if particle is out of bounds
void ParticleSystem1D::render() {
CRGB baseRGB;
if(framebuffer == nullptr) {
PSPRINTLN(F("PS render: no framebuffer!"));
return;
}
CRGBW baseRGB;
uint32_t brightness; // particle brightness, fades if dying
TBlendType blend = LINEARBLEND; // default color rendering: wrap palette
if (particlesettings.colorByAge || particlesettings.colorByPosition) {
blend = LINEARBLEND_NOWRAP;
}
#ifdef ESP8266 // no local buffer on ESP8266
if (motionBlur)
SEGMENT.fadeToBlackBy(255 - motionBlur);
else
SEGMENT.fill(BLACK); // clear the buffer before rendering to it
#else
if (motionBlur) { // blurring active
for (int32_t x = 0; x <= maxXpixel; x++) {
fast_color_scale(framebuffer[x], motionBlur);
framebuffer[x] = fast_color_scale(framebuffer[x], motionBlur);
}
}
else { // no blurring: clear buffer
memset(framebuffer, 0, (maxXpixel+1) * sizeof(CRGB));
memset(framebuffer, 0, (maxXpixel+1) * sizeof(CRGBW));
}
#endif
// go over particles and render them to the buffer
for (uint32_t i = 0; i < usedParticles; i++) {
if ( particles[i].ttl == 0 || particleFlags[i].outofbounds)
@@ -1469,48 +1453,39 @@ void ParticleSystem1D::render() {
if (advPartProps) { //saturation is advanced property in 1D system
if (advPartProps[i].sat < 255) {
CHSV32 baseHSV;
rgb2hsv((uint32_t((byte(baseRGB.r) << 16) | (byte(baseRGB.g) << 8) | (byte(baseRGB.b)))), baseHSV); // convert to HSV
rgb2hsv(baseRGB.color32, baseHSV); // convert to HSV
baseHSV.s = min(baseHSV.s, advPartProps[i].sat); // set the saturation but don't increase it
uint32_t tempcolor;
hsv2rgb(baseHSV, tempcolor); // convert back to RGB
baseRGB = (CRGB)tempcolor;
hsv2rgb(baseHSV, baseRGB.color32); // convert back to RGB
}
}
brightness = gamma8(brightness); // apply gamma correction, used for gamma-inverted brightness distribution
if(gammaCorrectCol) brightness = gamma8(brightness); // apply gamma correction, used for gamma-inverted brightness distribution
renderParticle(i, brightness, baseRGB, particlesettings.wrap);
}
// apply smear-blur to rendered frame
if (smearBlur) {
#ifdef ESP8266
SEGMENT.blur(smearBlur, true); // no local buffer on ESP8266
#else
blur1D(framebuffer, maxXpixel + 1, smearBlur, 0);
#endif
}
// add background color
uint32_t bg_color = SEGCOLOR(1);
CRGBW bg_color = SEGCOLOR(1);
if (bg_color > 0) { //if not black
CRGB bg_color_crgb = bg_color; // convert to CRGB
for (int32_t i = 0; i <= maxXpixel; i++) {
#ifdef ESP8266 // no local buffer on ESP8266
SEGMENT.addPixelColor(i, bg_color, true);
#else
fast_color_add(framebuffer[i], bg_color_crgb);
#endif
framebuffer[i] = fast_color_add(framebuffer[i], bg_color);
}
}
#ifndef ESP8266
// transfer the frame-buffer to segment
for (int x = 0; x <= maxXpixel; x++) {
SEGMENT.setPixelColor(x, framebuffer[x]);
#ifndef WLED_DISABLE_2D
// transfer local buffer to segment if using 1D->2D mapping
if(SEGMENT.is2D() && SEGMENT.map1D2D) {
for (int x = 0; x <= maxXpixel; x++) {
//for (int x = 0; x < SEGMENT.vLength(); x++) {
SEGMENT.setPixelColor(x, framebuffer[x]); // this applies the mapping
}
}
#endif
#endif
}
// calculate pixel positions and brightness distribution and render the particle to local buffer or global buffer
__attribute__((optimize("O2"))) void ParticleSystem1D::renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGB &color, const bool wrap) {
__attribute__((optimize("O2"))) void ParticleSystem1D::renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGBW &color, const bool wrap) {
uint32_t size = particlesize;
if (advPartProps) // use advanced size properties (1D system has no large size global rendering TODO: add large global rendering?)
size = advPartProps[particleindex].size;
@@ -1518,11 +1493,7 @@ __attribute__((optimize("O2"))) void ParticleSystem1D::renderParticle(const uint
if (size == 0) { //single pixel particle, can be out of bounds as oob checking is made for 2-pixel particles (and updating it uses more code)
uint32_t x = particles[particleindex].x >> PS_P_RADIUS_SHIFT_1D;
if (x <= (uint32_t)maxXpixel) { //by making x unsigned there is no need to check < 0 as it will overflow
#ifdef ESP8266 // no local buffer on ESP8266
SEGMENT.addPixelColor(x, color.scale8(brightness), true);
#else
fast_color_add(framebuffer[x], color, brightness);
#endif
framebuffer[x] = fast_color_add(framebuffer[x], color, brightness);
}
return;
}
@@ -1548,18 +1519,19 @@ __attribute__((optimize("O2"))) void ParticleSystem1D::renderParticle(const uint
// - scale brigthness with gamma correction (done in render())
// - apply inverse gamma correction to brightness values
// - gamma is applied again in show() -> the resulting brightness distribution is linear but gamma corrected in total
pxlbrightness[0] = gamma8inv(pxlbrightness[0]); // use look-up-table for invers gamma
pxlbrightness[1] = gamma8inv(pxlbrightness[1]);
if(gammaCorrectCol) {
pxlbrightness[0] = gamma8inv(pxlbrightness[0]); // use look-up-table for invers gamma
pxlbrightness[1] = gamma8inv(pxlbrightness[1]);
}
// check if particle has advanced size properties and buffer is available
if (advPartProps && advPartProps[particleindex].size > 1) {
CRGB renderbuffer[10]; // 10 pixel buffer
uint32_t renderbuffer[10]; // 10 pixel buffer
memset(renderbuffer, 0, sizeof(renderbuffer)); // clear buffer
//render particle to a bigger size
//particle size to pixels: 2 - 63 is 4 pixels, < 128 is 6pixels, < 192 is 8 pixels, bigger is 10 pixels
//first, render the pixel to the center of the renderbuffer, then apply 1D blurring
fast_color_add(renderbuffer[4], color, pxlbrightness[0]);
fast_color_add(renderbuffer[5], color, pxlbrightness[1]);
renderbuffer[4] = fast_color_add(renderbuffer[4], color, pxlbrightness[0]);
renderbuffer[5] = fast_color_add(renderbuffer[5], color, pxlbrightness[1]);
uint32_t rendersize = 2; // initialize render size, minimum is 4 pixels, it is incremented int he loop below to start with 4
uint32_t offset = 4; // offset to zero coordinate to write/read data in renderbuffer (actually needs to be 3, is decremented in the loop below)
uint32_t blurpasses = size/64 + 1; // number of blur passes depends on size, four passes max
@@ -1593,7 +1565,7 @@ __attribute__((optimize("O2"))) void ParticleSystem1D::renderParticle(const uint
#ifdef ESP8266 // no local buffer on ESP8266
SEGMENT.addPixelColor(xfb, renderbuffer[xrb], true);
#else
fast_color_add(framebuffer[xfb], renderbuffer[xrb]);
framebuffer[xfb] = fast_color_add(framebuffer[xfb], renderbuffer[xrb]);
#endif
}
}
@@ -1613,11 +1585,7 @@ __attribute__((optimize("O2"))) void ParticleSystem1D::renderParticle(const uint
}
for (uint32_t i = 0; i < 2; i++) {
if (pxlisinframe[i]) {
#ifdef ESP8266 // no local buffer on ESP8266
SEGMENT.addPixelColor(pixco[i], color.scale8((uint8_t)pxlbrightness[i]), true);
#else
fast_color_add(framebuffer[pixco[i]], color, pxlbrightness[i]);
#endif
framebuffer[pixco[i]] = fast_color_add(framebuffer[pixco[i]], color, pxlbrightness[i]);
}
}
}
@@ -1753,7 +1721,7 @@ __attribute__((optimize("O2"))) void ParticleSystem1D::collideParticles(PSpartic
// update size and pointers (memory location and size can change dynamically)
// note: do not access the PS class in FX befor running this function (or it messes up SEGENV.data)
void ParticleSystem1D::updateSystem(void) {
setSize(SEGMENT.virtualLength()); // update size
setSize(SEGMENT.vLength()); // update size
updatePSpointers(advPartProps != nullptr);
}
@@ -1768,15 +1736,16 @@ void ParticleSystem1D::updatePSpointers(bool isadvanced) {
particles = reinterpret_cast<PSparticle1D *>(this + 1); // pointer to particles
particleFlags = reinterpret_cast<PSparticleFlags1D *>(particles + numParticles); // pointer to particle flags
sources = reinterpret_cast<PSsource1D *>(particleFlags + numParticles); // pointer to source(s)
#ifdef ESP8266 // no local buffer on ESP8266
PSdataEnd = reinterpret_cast<uint8_t *>(sources + numSources);
#else
framebuffer = reinterpret_cast<CRGB *>(sources + numSources); // pointer to framebuffer
// align pointer after framebuffer to 4bytes
uintptr_t p = reinterpret_cast<uintptr_t>(framebuffer + (maxXpixel+1)); // maxXpixel is SEGMENT.virtualLength() - 1
p = (p + 3) & ~0x03; // align to 4-byte boundary
PSdataEnd = reinterpret_cast<uint8_t *>(p); // pointer to first available byte after the PS for FX additional data
#endif
PSdataEnd = reinterpret_cast<uint8_t *>(sources + numSources); // pointer to first available byte after the PS for FX additional data (already aligned to 4 byte boundary)
#ifndef WLED_DISABLE_2D
if(SEGMENT.is2D() && SEGMENT.map1D2D) {
framebuffer = reinterpret_cast<uint32_t *>(sources + numSources); // use local framebuffer for 1D->2D mapping
PSdataEnd = reinterpret_cast<uint8_t *>(framebuffer + SEGMENT.maxMappingLength()); // pointer to first available byte after the PS for FX additional data (still aligned to 4 byte boundary)
}
else
#endif
framebuffer = SEGMENT.getPixels(); // use segment buffer for standard 1D rendering
if (isadvanced) {
advPartProps = reinterpret_cast<PSadvancedParticle1D *>(PSdataEnd);
PSdataEnd = reinterpret_cast<uint8_t *>(advPartProps + numParticles); // since numParticles is a multiple of 4, this is always aligned to 4 bytes. No need to add padding bytes here
@@ -1797,32 +1766,20 @@ void ParticleSystem1D::updatePSpointers(bool isadvanced) {
//non class functions to use for initialization, fraction is uint8_t: 255 means 100%
uint32_t calculateNumberOfParticles1D(const uint32_t fraction, const bool isadvanced) {
uint32_t numberofParticles = SEGMENT.virtualLength(); // one particle per pixel (if possible)
#ifdef ESP8266
uint32_t particlelimit = ESP8266_MAXPARTICLES_1D; // maximum number of paticles allowed
#elif ARDUINO_ARCH_ESP32S2
uint32_t particlelimit = ESP32S2_MAXPARTICLES_1D; // maximum number of paticles allowed
#else
uint32_t particlelimit = ESP32_MAXPARTICLES_1D; // maximum number of paticles allowed
#endif
uint32_t particlelimit = MAXPARTICLES_1D; // maximum number of paticles allowed
numberofParticles = min(numberofParticles, particlelimit); // limit to particlelimit
if (isadvanced) // advanced property array needs ram, reduce number of particles to use the same amount
numberofParticles = (numberofParticles * sizeof(PSparticle1D)) / (sizeof(PSparticle1D) + sizeof(PSadvancedParticle1D));
numberofParticles = (numberofParticles * (fraction + 1)) >> 8; // calculate fraction of particles
numberofParticles = numberofParticles < 20 ? 20 : numberofParticles; // 20 minimum
numberofParticles = numberofParticles < 10 ? 10 : numberofParticles; // 10 minimum
//make sure it is a multiple of 4 for proper memory alignment (easier than using padding bytes)
numberofParticles = (numberofParticles+3) & ~0x03; // note: with a separate particle buffer, this is probably unnecessary
PSPRINTLN(" calc numparticles:" + String(numberofParticles))
PSPRINTLN(" calc numparticles:" + String(numberofParticles));
return numberofParticles;
}
uint32_t calculateNumberOfSources1D(const uint32_t requestedsources) {
#ifdef ESP8266
int numberofSources = max(1, min((int)requestedsources,ESP8266_MAXSOURCES_1D)); // limit
#elif ARDUINO_ARCH_ESP32S2
int numberofSources = max(1, min((int)requestedsources, ESP32S2_MAXSOURCES_1D)); // limit
#else
int numberofSources = max(1, min((int)requestedsources, ESP32_MAXSOURCES_1D)); // limit
#endif
int numberofSources = max(1, min((int)requestedsources,MAXSOURCES_1D)); // limit
// make sure it is a multiple of 4 for proper memory alignment (so minimum is acutally 4)
numberofSources = (numberofSources+3) & ~0x03;
return numberofSources;
@@ -1835,10 +1792,11 @@ bool allocateParticleSystemMemory1D(const uint32_t numparticles, const uint32_t
requiredmemory += sizeof(PSparticleFlags1D) * numparticles;
requiredmemory += sizeof(PSparticle1D) * numparticles;
requiredmemory += sizeof(PSsource1D) * numsources;
#ifndef ESP8266 // no local buffer on ESP8266
requiredmemory += sizeof(CRGB) * SEGMENT.virtualLength();
#endif
requiredmemory += additionalbytes + 3; // add 3 to ensure room for stuffing bytes to make it 4 byte aligned
#ifndef WLED_DISABLE_2D
if(SEGMENT.is2D())
requiredmemory += sizeof(uint32_t) * SEGMENT.maxMappingLength(); // need local buffer for mapped rendering
#endif
requiredmemory += additionalbytes;
if (isadvanced)
requiredmemory += sizeof(PSadvancedParticle1D) * numparticles;
return(SEGMENT.allocateData(requiredmemory));
@@ -1850,9 +1808,19 @@ bool initParticleSystem1D(ParticleSystem1D *&PartSys, const uint32_t requestedso
if (SEGLEN == 1) return false; // single pixel not supported
uint32_t numparticles = calculateNumberOfParticles1D(fractionofparticles, advanced);
uint32_t numsources = calculateNumberOfSources1D(requestedsources);
if (!allocateParticleSystemMemory1D(numparticles, numsources, advanced, additionalbytes)) {
DEBUG_PRINT(F("PS init failed: memory depleted"));
return false;
bool allocsuccess = false;
while(numparticles >= 10) { // make sure we have at least 10 particles or quit
if (allocateParticleSystemMemory1D(numparticles, numsources, advanced, additionalbytes)) {
PSPRINT(F("PS 1D alloc succeeded"));
allocsuccess = true;
break; // allocation succeeded
}
numparticles /= 2; // cut number of particles in half and try again
PSPRINTLN(F("PS 1D alloc failed, trying with less particles..."));
}
if (!allocsuccess) {
PSPRINTLN(F("PS init failed: memory depleted"));
return false; // allocation failed
}
PartSys = new (SEGENV.data) ParticleSystem1D(SEGMENT.virtualLength(), numparticles, numsources, advanced); // particle system constructor
return true;
@@ -1861,18 +1829,17 @@ bool initParticleSystem1D(ParticleSystem1D *&PartSys, const uint32_t requestedso
// blur a 1D buffer, sub-size blurring can be done using start and size
// for speed, 32bit variables are used, make sure to limit them to 8bit (0-255) or result is undefined
// to blur a subset of the buffer, change the size and set start to the desired starting coordinates
void blur1D(CRGB *colorbuffer, uint32_t size, uint32_t blur, uint32_t start)
void blur1D(uint32_t *colorbuffer, uint32_t size, uint32_t blur, uint32_t start)
{
CRGB seeppart, carryover;
CRGBW seeppart, carryover;
uint32_t seep = blur >> 1;
carryover = BLACK;
for (uint32_t x = start; x < start + size; x++) {
seeppart = colorbuffer[x]; // create copy of current color
fast_color_scale(seeppart, seep); // scale it and seep to neighbours
seeppart = fast_color_scale(colorbuffer[x], seep); // scale it and seep to neighbours
if (x > 0) {
fast_color_add(colorbuffer[x-1], seeppart);
if (carryover) // note: check adds overhead but is faster on average
fast_color_add(colorbuffer[x], carryover); // is black on first pass
colorbuffer[x-1] = fast_color_add(colorbuffer[x-1], seeppart);
if (carryover.color32) // note: check adds overhead but is faster on average
colorbuffer[x] = fast_color_add(colorbuffer[x], carryover); // is black on first pass
}
carryover = seeppart;
}
@@ -1921,23 +1888,14 @@ static bool checkBoundsAndWrap(int32_t &position, const int32_t max, const int32
return true; // particle is in bounds
}
// fastled color adding is very inaccurate in color preservation (but it is fast)
// a better color add function is implemented in colors.cpp but it uses 32bit RGBW. to use it colors need to be shifted just to then be shifted back by that function, which is slow
// this is a fast version for RGB (no white channel, PS does not handle white) and with native CRGB including scaling of second color
// note: result is stored in c1, not using a return value is faster as the CRGB struct does not need to be copied upon return
// note2: function is mainly used to add scaled colors, so checking if one color is black is slower
// note3: scale is 255 when using blur, checking for that makes blur faster
__attribute__((optimize("O2"))) static void fast_color_add(CRGB &c1, const CRGB &c2, const uint8_t scale) {
// this is a fast version for CRGBW color adding ignoring white channel (PS does not handle white) including scaling of second color
// note: function is mainly used to add scaled colors, so checking if one color is black is slower
// note2: returning CRGBW value is slightly slower as the return value gets written to uint32_t framebuffer
__attribute__((optimize("O2"))) static uint32_t fast_color_add(CRGBW c1, const CRGBW c2, const uint8_t scale) {
uint32_t r, g, b;
if (scale < 255) {
r = c1.r + ((c2.r * scale) >> 8);
g = c1.g + ((c2.g * scale) >> 8);
b = c1.b + ((c2.b * scale) >> 8);
} else {
r = c1.r + c2.r;
g = c1.g + c2.g;
b = c1.b + c2.b;
}
r = c1.r + ((c2.r * scale) >> 8);
g = c1.g + ((c2.g * scale) >> 8);
b = c1.b + ((c2.b * scale) >> 8);
// note: this chained comparison is the fastest method for max of 3 values (faster than std:max() or using xor)
uint32_t max = (r > g) ? ((r > b) ? r : b) : ((g > b) ? g : b);
@@ -1951,13 +1909,15 @@ static bool checkBoundsAndWrap(int32_t &position, const int32_t max, const int32
c1.g = (g * newscale) >> 16;
c1.b = (b * newscale) >> 16;
}
return c1.color32;
}
// faster than fastled color scaling as it does in place scaling
__attribute__((optimize("O2"))) static void fast_color_scale(CRGB &c, const uint8_t scale) {
// fast CRGBW color scaling ignoring white channel (PS does not handle white)
__attribute__((optimize("O2"))) static uint32_t fast_color_scale(CRGBW c, const uint8_t scale) {
c.r = ((c.r * scale) >> 8);
c.g = ((c.g * scale) >> 8);
c.b = ((c.b * scale) >> 8);
return c.color32;
}
#endif // !(defined(WLED_DISABLE_PARTICLESYSTEM2D) && defined(WLED_DISABLE_PARTICLESYSTEM1D))

View File

@@ -37,13 +37,20 @@ static inline int32_t limitSpeed(const int32_t speed) {
#endif
#ifndef WLED_DISABLE_PARTICLESYSTEM2D
// memory allocation
#define ESP8266_MAXPARTICLES 256 // enough up to 16x16 pixels
#define ESP8266_MAXSOURCES 24
#define ESP32S2_MAXPARTICLES 1024 // enough up to 32x32 pixels
#define ESP32S2_MAXSOURCES 64
#define ESP32_MAXPARTICLES 2048 // enough up to 64x32 pixels
#define ESP32_MAXSOURCES 128
// memory allocation (based on reasonable segment size and available FX memory)
#ifdef ESP8266
#define MAXPARTICLES_2D 256
#define MAXSOURCES_2D 24
#define SOURCEREDUCTIONFACTOR 8
#elif ARDUINO_ARCH_ESP32S2
#define MAXPARTICLES_2D 1024
#define MAXSOURCES_2D 64
#define SOURCEREDUCTIONFACTOR 6
#else
#define MAXPARTICLES_2D 2048
#define MAXSOURCES_2D 128
#define SOURCEREDUCTIONFACTOR 4
#endif
// particle dimensions (subpixel division)
#define PS_P_RADIUS 64 // subpixel size, each pixel is divided by this for particle movement (must be a power of 2)
@@ -188,7 +195,7 @@ public:
private:
//rendering functions
void render();
[[gnu::hot]] void renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGB& color, const bool wrapX, const bool wrapY);
[[gnu::hot]] void renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGBW& color, const bool wrapX, const bool wrapY);
//paricle physics applied by system if flags are set
void applyGravity(); // applies gravity to all particles
void handleCollisions();
@@ -200,13 +207,13 @@ private:
void getParticleXYsize(PSadvancedParticle *advprops, PSsizeControl *advsize, uint32_t &xsize, uint32_t &ysize);
[[gnu::hot]] void bounce(int8_t &incomingspeed, int8_t &parallelspeed, int32_t &position, const uint32_t maxposition); // bounce on a wall
// note: variables that are accessed often are 32bit for speed
CRGB *framebuffer; // local frame buffer for rendering
uint32_t *framebuffer; // frame buffer for rendering. note: using CRGBW as the buffer is slower, ESP compiler seems to optimize this better giving more consistent FPS
PSsettings2D particlesettings; // settings used when updating particles (can also used by FX to move sources), do not edit properties directly, use functions above
uint32_t numParticles; // total number of particles allocated by this system
uint32_t emitIndex; // index to count through particles to emit so searching for dead pixels is faster
int32_t collisionHardness;
uint32_t wallHardness;
uint32_t wallRoughness; // randomizes wall collisions
uint32_t wallRoughness; // randomizes wall collisions
uint32_t particleHardRadius; // hard surface radius of a particle, used for collision detection (32bit for speed)
uint16_t collisionStartIdx; // particle array start index for collision detection
uint8_t fireIntesity = 0; // fire intensity, used for fire mode (flash use optimization, better than passing an argument to render function)
@@ -219,7 +226,7 @@ private:
uint8_t smearBlur; // 2D smeared blurring of full frame
};
void blur2D(CRGB *colorbuffer, const uint32_t xsize, uint32_t ysize, const uint32_t xblur, const uint32_t yblur, const uint32_t xstart = 0, uint32_t ystart = 0, const bool isparticle = false);
void blur2D(uint32_t *colorbuffer, const uint32_t xsize, uint32_t ysize, const uint32_t xblur, const uint32_t yblur, const uint32_t xstart = 0, uint32_t ystart = 0, const bool isparticle = false);
// initialization functions (not part of class)
bool initParticleSystem2D(ParticleSystem2D *&PartSys, const uint32_t requestedsources, const uint32_t additionalbytes = 0, const bool advanced = false, const bool sizecontrol = false);
uint32_t calculateNumberOfParticles2D(const uint32_t pixels, const bool advanced, const bool sizecontrol);
@@ -232,12 +239,18 @@ bool allocateParticleSystemMemory2D(const uint32_t numparticles, const uint32_t
////////////////////////
#ifndef WLED_DISABLE_PARTICLESYSTEM1D
// memory allocation
#define ESP8266_MAXPARTICLES_1D 320
#define ESP8266_MAXSOURCES_1D 16
#define ESP32S2_MAXPARTICLES_1D 1300
#define ESP32S2_MAXSOURCES_1D 32
#define ESP32_MAXPARTICLES_1D 2600
#define ESP32_MAXSOURCES_1D 64
#ifdef ESP8266
#define MAXPARTICLES_1D 320
#define MAXSOURCES_1D 16
#elif ARDUINO_ARCH_ESP32S2
#define MAXPARTICLES_1D 1300
#define MAXSOURCES_1D 32
#else
#define MAXPARTICLES_1D 2600
#define MAXSOURCES_1D 64
#endif
// particle dimensions (subpixel division)
#define PS_P_RADIUS_1D 32 // subpixel size, each pixel is divided by this for particle movement, if this value is changed, also change the shift defines (next two lines)
@@ -351,7 +364,7 @@ public:
private:
//rendering functions
void render(void);
[[gnu::hot]] void renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGB &color, const bool wrap);
[[gnu::hot]] void renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGBW &color, const bool wrap);
//paricle physics applied by system if flags are set
void applyGravity(); // applies gravity to all particles
@@ -363,9 +376,7 @@ private:
//void updateSize(PSadvancedParticle *advprops, PSsizeControl *advsize); // advanced size control
[[gnu::hot]] void bounce(int8_t &incomingspeed, int8_t &parallelspeed, int32_t &position, const uint32_t maxposition); // bounce on a wall
// note: variables that are accessed often are 32bit for speed
#ifndef ESP8266
CRGB *framebuffer; // local frame buffer for rendering
#endif
uint32_t *framebuffer; // frame buffer for rendering. note: using CRGBW as the buffer is slower, ESP compiler seems to optimize this better giving more consistent FPS
PSsettings1D particlesettings; // settings used when updating particles
uint32_t numParticles; // total number of particles allocated by this system
uint32_t emitIndex; // index to count through particles to emit so searching for dead pixels is faster
@@ -386,5 +397,5 @@ bool initParticleSystem1D(ParticleSystem1D *&PartSys, const uint32_t requestedso
uint32_t calculateNumberOfParticles1D(const uint32_t fraction, const bool isadvanced);
uint32_t calculateNumberOfSources1D(const uint32_t requestedsources);
bool allocateParticleSystemMemory1D(const uint32_t numparticles, const uint32_t numsources, const bool isadvanced, const uint32_t additionalbytes);
void blur1D(CRGB *colorbuffer, uint32_t size, uint32_t blur, uint32_t start);
void blur1D(uint32_t *colorbuffer, uint32_t size, uint32_t blur, uint32_t start);
#endif // WLED_DISABLE_PARTICLESYSTEM1D

View File

@@ -5,6 +5,8 @@
#include <Arduino.h>
#include <IPAddress.h>
#ifdef ARDUINO_ARCH_ESP32
#include <ESPmDNS.h>
#include "src/dependencies/network/Network.h" // for isConnected() (& WiFi)
#include "driver/ledc.h"
#include "soc/ledc_struct.h"
#if !(defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3))
@@ -25,6 +27,7 @@
#include "bus_wrapper.h"
#include <bits/unique_ptr.h>
extern char cmDNS[];
extern bool cctICused;
extern bool useParallelI2S;
@@ -36,25 +39,32 @@ uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, const
//util.cpp
// PSRAM allocation wrappers
#ifndef ESP8266
#if !defined(ESP8266) && !defined(CONFIG_IDF_TARGET_ESP32C3)
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
inline void d_free(void *ptr) { heap_caps_free(ptr); }
}
#else
extern "C" {
void *realloc_malloc(void *ptr, size_t size);
}
#define p_malloc malloc
#define p_calloc calloc
#define p_realloc realloc
#define p_realloc_malloc realloc_malloc
#define p_free free
#define d_malloc malloc
#define d_calloc calloc
#define d_realloc realloc
#define d_realloc_malloc realloc_malloc
#define d_free free
#endif
@@ -95,7 +105,7 @@ void Bus::calculateCCT(uint32_t c, uint8_t &ww, uint8_t &cw) {
} else {
cct = (approximateKelvinFromRGB(c) - 1900) >> 5; // convert K (from RGB value) to relative format
}
//0 - linear (CCT 127 = 50% warm, 50% cold), 127 - additive CCT blending (CCT 127 = 100% warm, 100% cold)
if (cct < _cctBlend) ww = 255;
else ww = ((255-cct) * 255) / (255 - _cctBlend);
@@ -153,6 +163,10 @@ BusDigital::BusDigital(const BusConfig &bc, uint8_t nr)
if (bc.type == TYPE_WS2812_1CH_X3) lenToCreate = NUM_ICS_WS2812_1CH_3X(bc.count); // only needs a third of "RGB" LEDs for NeoPixelBus
_busPtr = PolyBus::create(_iType, _pins, lenToCreate + _skip, nr);
_valid = (_busPtr != nullptr) && bc.count > 0;
// fix for wled#4759
if (_valid) for (unsigned i = 0; i < _skip; i++) {
PolyBus::setPixelColor(_busPtr, _iType, i, 0, COL_ORDER_GRB); // set sacrificial pixels to black (CO does not matter here)
}
DEBUGBUS_PRINTF_P(PSTR("Bus: %successfully inited #%u (len:%u, type:%u (RGB:%d, W:%d, CCT:%d), pins:%u,%u [itype:%u] mA=%d/%d)\n"),
_valid?"S":"Uns",
(int)nr,
@@ -230,18 +244,18 @@ void BusDigital::show() {
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 < _bri) {
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
}
if (newBri < _bri) {
PolyBus::setBrightness(_busPtr, _iType, newBri); // limit brightness to stay within current limits
unsigned hwLen = _len;
if (_type == TYPE_WS2812_1CH_X3) hwLen = NUM_ICS_WS2812_1CH_3X(_len); // only needs a third of "RGB" LEDs for NeoPixelBus
for (unsigned i = 0; i < hwLen; i++) {
// use 0 as color order, actual order does not matter here as we just update the channel values as-is
uint32_t c = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, i, 0), _bri);
if (hasCCT()) Bus::calculateCCT(c, cctWW, cctCW); // this will unfortunately corrupt (segment) CCT data on every bus
PolyBus::setPixelColor(_busPtr, _iType, i, c, 0, (cctCW<<8) | cctWW); // repaint all pixels with new brightness
}
PolyBus::show(_busPtr, _iType, false); // faster if buffer consistency is not important
}
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)
@@ -326,7 +340,7 @@ size_t BusDigital::getPins(uint8_t* pinArray) const {
}
size_t BusDigital::getBusSize() const {
return sizeof(BusDigital) + (isOk() ? PolyBus::getDataSize(_busPtr, _iType) /*+ (_data ? _len * getNumberOfChannels() : 0)*/ : 0);
return sizeof(BusDigital) + (isOk() ? PolyBus::getDataSize(_busPtr, _iType) : 0);
}
void BusDigital::setColorOrder(uint8_t colorOrder) {
@@ -688,6 +702,10 @@ BusNetwork::BusNetwork(const BusConfig &bc)
_hasCCT = false;
_UDPchannels = _hasWhite + 3;
_client = IPAddress(bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]);
#ifdef ARDUINO_ARCH_ESP32
_hostname = bc.text;
resolveHostname(); // resolve hostname to IP address if needed
#endif
_data = (uint8_t*)d_calloc(_len, _UDPchannels);
_valid = (_data != nullptr);
DEBUGBUS_PRINTF_P(PSTR("%successfully inited virtual strip with type %u and IP %u.%u.%u.%u\n"), _valid?"S":"Uns", bc.type, bc.pins[0], bc.pins[1], bc.pins[2], bc.pins[3]);
@@ -722,6 +740,19 @@ size_t BusNetwork::getPins(uint8_t* pinArray) const {
return 4;
}
#ifdef ARDUINO_ARCH_ESP32
void BusNetwork::resolveHostname() {
static unsigned long nextResolve = 0;
if (Network.isConnected() && millis() > nextResolve && _hostname.length() > 0) {
nextResolve = millis() + 600000; // resolve only every 10 minutes
IPAddress clnt;
if (strlen(cmDNS) > 0) clnt = MDNS.queryHost(_hostname);
else WiFi.hostByName(_hostname.c_str(), clnt);
if (clnt != IPAddress()) _client = clnt;
}
}
#endif
// credit @willmmiles & @netmindz https://github.com/wled/WLED/pull/4056
std::vector<LEDType> BusNetwork::getLEDTypes() {
return {
@@ -907,6 +938,13 @@ void BusManager::on() {
}
}
}
#else
for (auto &bus : busses) if (bus->isVirtual()) {
// virtual/network bus should check for IP change if hostname is specified
// otherwise there are no endpoints to force DNS resolution
BusNetwork &b = static_cast<BusNetwork&>(*bus);
b.resolveHostname();
}
#endif
#ifdef ESP32_DATA_IDLE_HIGH
esp32RMTInvertIdle();

View File

@@ -133,6 +133,7 @@ class Bus {
virtual uint16_t getUsedCurrent() const { return 0; }
virtual uint16_t getMaxCurrent() const { return 0; }
virtual size_t getBusSize() const { return sizeof(Bus); }
virtual const String getCustomText() const { return String(); }
inline bool hasRGB() const { return _hasRgb; }
inline bool hasWhite() const { return _hasWhite; }
@@ -215,7 +216,7 @@ class Bus {
uint8_t _autoWhiteMode;
// global Auto White Calculation override
static uint8_t _gAWM;
// _cct has the following menaings (see calculateCCT() & BusManager::setSegmentCCT()):
// _cct has the following meanings (see calculateCCT() & BusManager::setSegmentCCT()):
// -1 means to extract approximate CCT value in K from RGB (in calcualteCCT())
// [0,255] is the exact CCT value where 0 means warm and 255 cold
// [1900,10060] only for color correction expressed in K (colorBalanceFromKelvin())
@@ -342,6 +343,10 @@ class BusNetwork : public Bus {
size_t getBusSize() const override { return sizeof(BusNetwork) + (isOk() ? _len * _UDPchannels : 0); }
void show() override;
void cleanup();
#ifdef ARDUINO_ARCH_ESP32
void resolveHostname();
const String getCustomText() const override { return _hostname; }
#endif
static std::vector<LEDType> getLEDTypes();
@@ -351,6 +356,9 @@ class BusNetwork : public Bus {
uint8_t _UDPchannels;
bool _broadcastLock;
uint8_t *_data;
#ifdef ARDUINO_ARCH_ESP32
String _hostname;
#endif
};
@@ -368,8 +376,9 @@ struct BusConfig {
uint16_t frequency;
uint8_t milliAmpsPerLed;
uint16_t milliAmpsMax;
String text;
BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY, uint16_t clock_kHz=0U, uint8_t maPerLed=LED_MILLIAMPS_DEFAULT, uint16_t maMax=ABL_MILLIAMPS_DEFAULT)
BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY, uint16_t clock_kHz=0U, uint8_t maPerLed=LED_MILLIAMPS_DEFAULT, uint16_t maMax=ABL_MILLIAMPS_DEFAULT, String sometext = "")
: count(std::max(len,(uint16_t)1))
, start(pstart)
, colorOrder(pcolorOrder)
@@ -379,6 +388,7 @@ struct BusConfig {
, frequency(clock_kHz)
, milliAmpsPerLed(maPerLed)
, milliAmpsMax(maMax)
, text(sometext)
{
refreshReq = (bool) GET_BIT(busType,7);
type = busType & 0x7F; // bit 7 may be/is hacked to include refresh info (1=refresh in off state, 0=no refresh)

View File

@@ -469,12 +469,20 @@ class PolyBus {
}
static void* create(uint8_t busType, uint8_t* pins, uint16_t len, uint8_t channel) {
#if defined(ARDUINO_ARCH_ESP32) && !(defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3))
// NOTE: "channel" is only used on ESP32 (and its variants) for RMT channel allocation
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
if (_useParallelI2S && (channel >= 8)) {
// Parallel I2S channels are to be used first, so subtract 8 to get the RMT channel number
channel -= 8;
}
#endif
#if defined(ARDUINO_ARCH_ESP32) && !(defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3))
// since 0.15.0-b3 I2S1 is favoured for classic ESP32 and moved to position 0 (channel 0) so we need to subtract 1 for correct RMT allocation
if (!_useParallelI2S && channel > 0) channel--; // accommodate I2S1 which is used as 1st bus on classic ESP32
// if user selected parallel I2S, RMT is used 1st (8 channels) followed by parallel I2S (8 channels)
#endif
void* busPtr = nullptr;
switch (busType) {
case I_NONE: break;
@@ -862,12 +870,12 @@ class PolyBus {
// 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))->SetPixelColor(pix, RgbColor(col)); else (static_cast<B_32_I2_NEO_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); break;
case I_32_I2_NEO_4: if (_useParallelI2S) (static_cast<B_32_IP_NEO_4*>(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast<B_32_I2_NEO_4*>(busPtr))->SetPixelColor(pix, col); break;
case I_32_I2_NEO_4: if (_useParallelI2S) (static_cast<B_32_IP_NEO_4*>(busPtr))->SetPixelColor(pix, col); else (static_cast<B_32_I2_NEO_4*>(busPtr))->SetPixelColor(pix, col); break;
case I_32_I2_400_3: if (_useParallelI2S) (static_cast<B_32_IP_400_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast<B_32_I2_400_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); break;
case I_32_I2_TM1_4: if (_useParallelI2S) (static_cast<B_32_IP_TM1_4*>(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast<B_32_I2_TM1_4*>(busPtr))->SetPixelColor(pix, col); break;
case I_32_I2_TM1_4: if (_useParallelI2S) (static_cast<B_32_IP_TM1_4*>(busPtr))->SetPixelColor(pix, col); else (static_cast<B_32_I2_TM1_4*>(busPtr))->SetPixelColor(pix, col); break;
case I_32_I2_TM2_3: if (_useParallelI2S) (static_cast<B_32_IP_TM2_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast<B_32_I2_TM2_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); break;
case I_32_I2_UCS_3: if (_useParallelI2S) (static_cast<B_32_IP_UCS_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast<B_32_I2_UCS_3*>(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break;
case I_32_I2_UCS_4: if (_useParallelI2S) (static_cast<B_32_IP_UCS_4*>(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast<B_32_I2_UCS_4*>(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break;
case I_32_I2_UCS_3: if (_useParallelI2S) (static_cast<B_32_IP_UCS_3*>(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); else (static_cast<B_32_I2_UCS_3*>(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break;
case I_32_I2_UCS_4: if (_useParallelI2S) (static_cast<B_32_IP_UCS_4*>(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); else (static_cast<B_32_I2_UCS_4*>(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break;
case I_32_I2_APA106_3: if (_useParallelI2S) (static_cast<B_32_IP_APA106_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast<B_32_I2_APA106_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); break;
case I_32_I2_FW6_5: if (_useParallelI2S) (static_cast<B_32_IP_FW6_5*>(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); else (static_cast<B_32_I2_FW6_5*>(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break;
case I_32_I2_2805_5: if (_useParallelI2S) (static_cast<B_32_IP_2805_5*>(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); else (static_cast<B_32_I2_2805_5*>(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break;
@@ -1423,12 +1431,13 @@ class PolyBus {
return I_8266_U0_SM16825_5 + offset;
}
#else //ESP32
uint8_t offset = 0; // 0 = RMT (num 1-8), 1 = I2S0 (used by Audioreactive), 2 = I2S1
uint8_t offset = 0; // 0 = RMT (num 1-8), 1 = I2S1 [I2S0 is used by Audioreactive]
#if defined(CONFIG_IDF_TARGET_ESP32S2)
// ESP32-S2 only has 4 RMT channels
if (_useParallelI2S) {
if (num > 11) return I_NONE;
if (num > 3) offset = 1; // use x8 parallel I2S0 channels (use last to allow Audioreactive)
if (num < 8) offset = 1; // use x8 parallel I2S0 channels followed by RMT
// Note: conflicts with AudioReactive if enabled
} else {
if (num > 4) return I_NONE;
if (num > 3) offset = 1; // only one I2S0 (use last to allow Audioreactive)
@@ -1441,7 +1450,7 @@ class PolyBus {
// On ESP32-S3 only the first 4 RMT channels are usable for transmitting
if (_useParallelI2S) {
if (num > 11) return I_NONE;
if (num > 3) offset = 1; // use x8 parallel I2S LCD channels
if (num < 8) offset = 1; // use x8 parallel I2S LCD channels, followed by RMT
} else {
if (num > 3) return I_NONE; // do not use single I2S (as it is not supported)
}
@@ -1449,7 +1458,7 @@ class PolyBus {
// standard ESP32 has 8 RMT and x1/x8 I2S1 channels
if (_useParallelI2S) {
if (num > 15) return I_NONE;
if (num > 7) offset = 1; // 8 RMT followed by 8 I2S
if (num < 8) offset = 1; // 8 I2S followed by 8 RMT
} else {
if (num > 9) return I_NONE;
if (num == 0) offset = 1; // prefer I2S1 for 1st bus (less flickering but more RAM needed)

View File

@@ -235,7 +235,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
}
ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh
busConfigs.emplace_back(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, maPerLed, maMax);
String host = elm[F("text")] | String();
busConfigs.emplace_back(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, maPerLed, maMax, host);
doInitBusses = true; // finalization done in beginStrip()
if (!Bus::isVirtual(ledType)) s++; // have as many virtual buses as you want
}
@@ -970,6 +971,7 @@ void serializeConfig(JsonObject root) {
ins[F("freq")] = bus->getFrequency();
ins[F("maxpwr")] = bus->getMaxCurrent();
ins[F("ledma")] = bus->getLEDCurrent();
ins[F("text")] = bus->getCustomText();
}
JsonArray hw_com = hw.createNestedArray(F("com"));

View File

@@ -86,6 +86,23 @@ uint32_t color_fade(uint32_t c1, uint8_t amount, bool video)
return scaledcolor;
}
/*
* color adjustment in HSV color space (converts RGB to HSV and back), color conversions are not 100% accurate!
shifts hue, increase brightness, decreases saturation (if not black)
note: inputs are 32bit to speed up the function, useful input value ranges are 0-255
*/
uint32_t adjust_color(uint32_t rgb, uint32_t hueShift, uint32_t lighten, uint32_t brighten) {
if(rgb == 0 | hueShift + lighten + brighten == 0) return rgb; // black or no change
CHSV32 hsv;
rgb2hsv(rgb, hsv); //convert to HSV
hsv.h += (hueShift << 8); // shift hue (hue is 16 bits)
hsv.s = max((int32_t)0, (int32_t)hsv.s - (int32_t)lighten); // desaturate
hsv.v = min((uint32_t)255, (uint32_t)hsv.v + brighten); // increase brightness
uint32_t rgb_adjusted;
hsv2rgb(hsv, rgb_adjusted); // convert back to RGB TODO: make this into 16 bit conversion
return rgb_adjusted;
}
// 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)
{

View File

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

View File

@@ -13,7 +13,7 @@ function isN(n) { return !isNaN(parseFloat(n)) && isFinite(n); } // isNumber
// https://stackoverflow.com/questions/3885817/how-do-i-check-that-a-number-is-float-or-integer
function isF(n) { return n === +n && n !== (n|0); } // isFloat
function isI(n) { return n === +n && n === (n|0); } // isInteger
function toggle(el) { gId(el).classList.toggle("hide"); gId('No'+el).classList.toggle("hide"); }
function toggle(el) { gId(el).classList.toggle("hide"); let n = gId('No'+el); if (n) n.classList.toggle("hide"); }
function tooltip(cont=null) {
d.querySelectorAll((cont?cont+" ":"")+"[title]").forEach((element)=>{
element.addEventListener("pointerover", ()=>{

View File

@@ -329,7 +329,7 @@
<div>
<button class="btn ibtn" onclick="requestJson()">Refresh</button>
<button class="btn ibtn" onclick="toggleNodes()">Instance List</button>
<button class="btn ibtn" onclick="window.open(getURL('/update'),'_self');">Update WLED</button>
<button class="btn ibtn" onclick="window.open(getURL('/update'),'_self');" id="updBt">Update WLED</button>
<button class="btn ibtn" id="resetbtn" onclick="cnfReset()">Reboot WLED</button>
</div>
<br>

View File

@@ -685,6 +685,7 @@ function parseInfo(i) {
gId("filter2D").classList.remove('hide');
gId('bs').querySelectorAll('option[data-type="2D"]').forEach((o,i)=>{o.style.display='';});
}
gId("updBt").style.display = (i.opt & 1) ? '':'none';
// if (i.noaudio) {
// gId("filterVol").classList.add("hide");
// gId("filterFreq").classList.add("hide");
@@ -1527,6 +1528,9 @@ function readState(s,command=false)
case 3:
errstr = "Buffer locked!";
break;
case 7:
errstr = "No RAM for buffer!";
break;
case 8:
errstr = "Effect RAM depleted!";
break;

View File

@@ -6,8 +6,7 @@
<title>LED Settings</title>
<script src="common.js" async type="text/javascript"></script>
<script>
var maxB=1,maxD=1,maxA=1,maxV=0,maxM=4000,maxPB=2048,maxL=1664,maxCO=5,maxBT=10; //maximum bytes for LED allocation: 4kB for 8266, 32kB for 32
var oMaxB=1;
var maxB=1,maxD=1,maxA=1,maxV=0,maxM=4000,maxPB=2048,maxL=1664,maxCO=5; //maximum bytes for LED allocation: 4kB for 8266, 32kB for 32
var customStarts=false,startsDirty=[];
function off(n) { gN(n).value = -1;}
// these functions correspond to C macros found in const.h
@@ -42,9 +41,9 @@
}); // If we set async false, file is loaded and executed, then next statement is processed
if (loc) d.Sf.action = getURL('/settings/leds');
}
function bLimits(b,v,p,m,l,o=5,d=2,a=6,n=10) {
oMaxB = maxB = b; // maxB - max buses (can be changed if using ESP32 parallel I2S): 20 - ESP32, 14 - S3/S2, 6 - C3, 4 - 8266
maxD = d; // maxD - max digital channels (can be changed if using ESP32 parallel I2S): 17 - ESP32, 12 - S3/S2, 2 - C3, 3 - 8266
function bLimits(b,v,p,m,l,o=5,d=2,a=6) {
maxB = b; // maxB - max physical (analog + digital) buses: 32 - ESP32, 14 - S3/S2, 6 - C3, 4 - 8266
maxD = d; // maxD - max digital channels (can be changed if using ESP32 parallel I2S): 16 - ESP32, 12 - S3/S2, 2 - C3, 3 - 8266
maxA = a; // maxA - max analog channels: 16 - ESP32, 8 - S3/S2, 6 - C3, 5 - 8266
maxV = v; // maxV - min virtual buses: 6 - ESP32/S3, 4 - S2/C3, 3 - ESP8266 (only used to distinguish S2/S3)
maxPB = p; // maxPB - max LEDs per bus
@@ -53,6 +52,11 @@
maxCO = o; // maxCO - max Color Order mappings
maxBT = n; // maxBT - max buttons
}
function is8266() { return maxA == 5 && maxD == 3; } // NOTE: see const.h
function is32() { return maxA == 16 && maxD == 16; } // NOTE: see const.h
function isC3() { return maxA == 6 && maxD == 2; } // NOTE: see const.h
function isS2() { return maxA == 8 && maxD == 12 && maxV == 4; } // NOTE: see const.h
function isS3() { return maxA == 8 && maxD == 12 && maxV == 6; } // NOTE: see const.h
function pinsOK() {
var ok = true;
var nList = d.Sf.querySelectorAll("#mLC input[name^=L]");
@@ -213,7 +217,6 @@
let busMA = 0;
let sLC = 0, sPC = 0, sDI = 0, maxLC = 0;
const abl = d.Sf.ABL.checked;
maxB = oMaxB; // TODO make sure we start with all possible buses
let setPinConfig = (n,t) => {
let p0d = "GPIO:";
let p1d = "";
@@ -272,7 +275,7 @@
gRGBW |= hasW(t); // RGBW checkbox
gId("co"+n).style.display = (isVir(t) || isAna(t)) ? "none":"inline"; // hide color order for PWM
gId("dig"+n+"w").style.display = (isDig(t) && hasW(t)) ? "inline":"none"; // show swap channels dropdown
gId("dig"+n+"w").querySelector("[data-opt=CCT]").disabled = !hasCCT(t); // disable WW/CW swapping
gId("dig"+n+"w").querySelector("[data-opt=CCT]").disabled = !hasCCT(t); // disable WW/CW swapping
if (!(isDig(t) && hasW(t))) d.Sf["WO"+n].value = 0; // reset swapping
gId("dig"+n+"c").style.display = (isAna(t)) ? "none":"inline"; // hide count for analog
gId("dig"+n+"r").style.display = (isVir(t)) ? "none":"inline"; // hide reversed for virtual
@@ -282,6 +285,8 @@
gId("dig"+n+"l").style.display = (isD2P(t) || isPWM(t)) ? "inline":"none"; // bus clock speed / PWM speed (relative) (not On/Off)
gId("rev"+n).innerHTML = isAna(t) ? "Inverted output":"Reversed"; // change reverse text for analog else (rotated 180°)
//gId("psd"+n).innerHTML = isAna(t) ? "Index:":"Start:"; // change analog start description
gId("net"+n+"h").style.display = isNet(t) && !is8266() ? "block" : "none"; // show host field for network types except on ESP8266
if (!isNet(t) || is8266()) d.Sf["HS"+n].value = ""; // cleart host field if not network type or ESP8266
});
// display global white channel overrides
gId("wc").style.display = (gRGBW) ? 'inline':'none';
@@ -290,11 +295,16 @@
d.Sf.CR.checked = false;
}
// update start indexes, max values, calculate current, etc
let sameType = 0;
var nList = d.Sf.querySelectorAll("#mLC input[name^=L]");
nList.forEach((LC,i)=>{
let nm = LC.name.substring(0,2); // field name
let n = LC.name.substring(2); // bus number
let t = parseInt(d.Sf["LT"+n].value); // LED type SELECT
if (isDig(t)) {
if (sameType == 0) sameType = t; // first bus type
else if (sameType != t) sameType = -1; // different bus type
}
// do we have a led count field
if (nm=="LC") {
let c = parseInt(LC.value,10); //get LED count
@@ -352,18 +362,13 @@
else LC.style.color = d.ro_gpio.some((e)=>e==parseInt(LC.value)) ? "orange" : "#fff";
}
});
const S2 = (oMaxB == 14) && (maxV == 4);
const S3 = (oMaxB == 14) && (maxV == 6);
if (oMaxB == 32 || S2 || S3) { // TODO: crude ESP32 & S2/S3 detection
if (maxLC > 300 || dC <= 2) {
if (is32() || isS2() || isS3()) {
if (maxLC > 600 || dC < 2 || sameType <= 0) {
d.Sf["PR"].checked = false;
gId("prl").classList.add("hide");
} else
gId("prl").classList.remove("hide");
// S2 supports mono I2S as well as parallel so we need to take that into account; S3 only supports parallel
maxD = (S2 || S3 ? 4 : 8) + (d.Sf["PR"].checked ? 8 : S2); // TODO: use bLimits() : 4/8RMT + (x1/x8 parallel) I2S1
maxB = oMaxB - (d.Sf["PR"].checked ? 0 : 7 + S3); // S2 (maxV==4) does support mono I2S
}
} else d.Sf["PR"].checked = false;
// distribute ABL current if not using PPL
enPPL(sDI);
@@ -464,6 +469,7 @@ mA/LED: <select name="LAsel${s}" onchange="enLA(this,'${s}');UI();">
<span id="p2d${s}"></span><input type="number" name="L2${s}" class="s" onchange="UI();pinUpd(this);"/>
<span id="p3d${s}"></span><input type="number" name="L3${s}" class="s" onchange="UI();pinUpd(this);"/>
<span id="p4d${s}"></span><input type="number" name="L4${s}" class="s" onchange="UI();pinUpd(this);"/>
<div id="net${s}h" class="hide">Host: <input type="text" name="HS${s}" maxlength="32" pattern="[a-zA-Z0-9_\\-]*" onchange="UI()"/>.local</div>
<div id="dig${s}r" style="display:inline"><br><span id="rev${s}">Reversed</span>: <input type="checkbox" name="CV${s}"></div>
<div id="dig${s}s" style="display:inline"><br>Skip first LEDs: <input type="number" name="SL${s}" min="0" max="255" value="0" oninput="UI()"></div>
<div id="dig${s}f" style="display:inline"><br><span id="off${s}">Off Refresh</span>: <input id="rf${s}" type="checkbox" name="RF${s}"></div>
@@ -480,15 +486,17 @@ mA/LED: <select name="LAsel${s}" onchange="enLA(this,'${s}');UI();">
if (type.t != undefined && type.t != "") {
opt.setAttribute('data-type', type.t);
}
sel.appendChild(opt);
sel.appendChild(opt);
}
}
});
enLA(d.Sf["LAsel"+s],s); // update LED mA
// disable inappropriate LED types
let sel = d.getElementsByName("LT"+s)[0]
if (i >= maxB || digitalB >= maxD) disable(sel,'option[data-type="D"]'); // NOTE: see isDig()
if (i >= maxB || twopinB >= 1) disable(sel,'option[data-type="2P"]'); // NOTE: see isD2P()
let sel = d.getElementsByName("LT"+s)[0];
// 32 & S2 supports mono I2S as well as parallel so we need to take that into account; S3 only supports parallel
let maxDB = maxD - (is32() || isS2() || isS3() ? (!d.Sf["PR"].checked)*8 - (!isS3()) : 0); // adjust max digital buses if parallel I2S is not used
if (digitalB >= maxDB) disable(sel,'option[data-type="D"]'); // NOTE: see isDig()
if (twopinB >= 2) disable(sel,'option[data-type="2P"]'); // NOTE: see isD2P() (we will only allow 2 2pin buses)
disable(sel,`option[data-type^="${'A'.repeat(maxA-analogB+1)}"]`); // NOTE: see isPWM()
sel.selectedIndex = sel.querySelector('option:not(:disabled)').index;
}
@@ -602,7 +610,7 @@ Swap: <select id="xw${s}" name="XW${s}">
var cs = false;
for (var i=1; i < gEBCN("iST").length; i++) {
var s = chrID(i);
var p = chrID(i-1); // cover edge case 'A' previous char being '9'
var p = chrID(i-1); // cover edge case 'A' previous char being '9'
var v = parseInt(gId("ls"+p).value) + parseInt(gN("LC"+p).value);
if (v != parseInt(gId("ls"+s).value)) {cs = true; startsDirty[i] = true;}
}
@@ -633,7 +641,7 @@ Swap: <select id="xw${s}" name="XW${s}">
function receivedText(e) {
let lines = e.target.result;
let c = JSON.parse(lines);
let c = JSON.parse(lines);
if (c.hw) {
if (c.hw.led) {
// remove all existing outputs

View File

@@ -53,13 +53,13 @@
Factory reset: <input type="checkbox" name="RS"><br>
All settings and presets will be erased.<br><br>
<div class="warn">&#9888; Unencrypted transmission. An attacker on the same network can intercept form data!</div>
<hr>
<span id="OTA"><hr>
<h3>Software Update</h3>
<button type="button" onclick="U()">Manual OTA Update</button><br>
<div id="aOTA">Enable ArduinoOTA: <input type="checkbox" name="AO"></div>
Only allow update from same network/WiFi: <input type="checkbox" name="SU"><br>
<i class="warn">&#9888; If you are using multiple VLANs (i.e. IoT or guest network) either set PIN or disable this option.<br>
Disabling this option will make your device less secure.</i><br>
Disabling this option will make your device less secure.</i><br></span>
<hr id="backup">
<h3>Backup & Restore</h3>
<div class="warn">&#9888; Restoring presets/configuration will OVERWRITE your current presets/configuration.<br>

View File

@@ -261,7 +261,7 @@ Static subnet mask:<br>
<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 Tetra</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

@@ -79,6 +79,9 @@ input {
input:disabled {
color: #888;
}
input:invalid {
color: #f00;
}
input[type="text"],
input[type="number"],
input[type="password"],
@@ -202,4 +205,4 @@ td {
#btns select {
width: 144px;
}
}
}

View File

@@ -174,6 +174,7 @@ class NeoGammaWLEDMethod {
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();
@@ -550,25 +551,32 @@ inline uint8_t hw_random8(uint32_t upperlimit) { return (hw_random8() * upperlim
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
#ifndef ESP8266
#if !defined(ESP8266) && !defined(CONFIG_IDF_TARGET_ESP32C3)
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
inline void d_free(void *ptr) { heap_caps_free(ptr); }
}
#else
extern "C" {
void *realloc_malloc(void *ptr, size_t size);
}
#define p_malloc malloc
#define p_calloc calloc
#define p_realloc realloc
#define p_realloc_malloc realloc_malloc
#define p_free free
#define d_malloc malloc
#define d_calloc calloc
#define d_realloc realloc
#define d_realloc_malloc realloc_malloc
#define d_free free
#endif

View File

@@ -11,6 +11,11 @@
#warning "MQTT topics length > 32 is not recommended for compatibility with usermods!"
#endif
static const char* sTopicFormat PROGMEM = "%.*s/%s";
// parse payload for brightness, ON/OFF or toggle
// briLast is used to remember last brightness value in case of ON/OFF or toggle
// bri is set to 0 if payload is "0" or "OFF" or "false"
static void parseMQTTBriPayload(char* payload)
{
if (strstr(payload, "ON") || strstr(payload, "on") || strstr(payload, "true")) {bri = briLast; stateUpdated(CALL_MODE_DIRECT_CHANGE);}
@@ -30,22 +35,18 @@ static void onMqttConnect(bool sessionPresent)
char subuf[MQTT_MAX_TOPIC_LEN + 9];
if (mqttDeviceTopic[0] != 0) {
strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1);
mqtt->subscribe(mqttDeviceTopic, 0);
snprintf_P(subuf, sizeof(subuf)-1, sTopicFormat, MQTT_MAX_TOPIC_LEN, mqttDeviceTopic, "col");
mqtt->subscribe(subuf, 0);
strcat_P(subuf, PSTR("/col"));
mqtt->subscribe(subuf, 0);
strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1);
strcat_P(subuf, PSTR("/api"));
snprintf_P(subuf, sizeof(subuf)-1, sTopicFormat, MQTT_MAX_TOPIC_LEN, mqttDeviceTopic, "api");
mqtt->subscribe(subuf, 0);
}
if (mqttGroupTopic[0] != 0) {
strlcpy(subuf, mqttGroupTopic, MQTT_MAX_TOPIC_LEN + 1);
mqtt->subscribe(mqttGroupTopic, 0);
snprintf_P(subuf, sizeof(subuf)-1, sTopicFormat, MQTT_MAX_TOPIC_LEN, mqttGroupTopic, "col");
mqtt->subscribe(subuf, 0);
strcat_P(subuf, PSTR("/col"));
mqtt->subscribe(subuf, 0);
strlcpy(subuf, mqttGroupTopic, MQTT_MAX_TOPIC_LEN + 1);
strcat_P(subuf, PSTR("/api"));
snprintf_P(subuf, sizeof(subuf)-1, sTopicFormat, MQTT_MAX_TOPIC_LEN, mqttGroupTopic, "api");
mqtt->subscribe(subuf, 0);
}
@@ -54,8 +55,7 @@ static void onMqttConnect(bool sessionPresent)
DEBUG_PRINTLN(F("MQTT ready"));
#ifndef USERMOD_SMARTNEST
strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1);
strcat_P(subuf, PSTR("/status"));
snprintf_P(subuf, sizeof(subuf)-1, sTopicFormat, MQTT_MAX_TOPIC_LEN, mqttDeviceTopic, "status");
mqtt->publish(subuf, 0, true, "online"); // retain message for a LWT
#endif
@@ -136,7 +136,7 @@ static void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProp
}
// Print adapter for flat buffers
namespace {
namespace {
class bufferPrint : public Print {
char* _buf;
size_t _size, _offset;
@@ -172,21 +172,21 @@ void publishMqtt()
char subuf[MQTT_MAX_TOPIC_LEN + 16];
sprintf_P(s, PSTR("%u"), bri);
strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1);
strcat_P(subuf, PSTR("/g"));
snprintf_P(subuf, sizeof(subuf)-1, sTopicFormat, MQTT_MAX_TOPIC_LEN, mqttDeviceTopic, "g");
mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263)
sprintf_P(s, PSTR("#%06X"), (colPri[3] << 24) | (colPri[0] << 16) | (colPri[1] << 8) | (colPri[2]));
strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1);
strcat_P(subuf, PSTR("/c"));
snprintf_P(subuf, sizeof(subuf)-1, sTopicFormat, MQTT_MAX_TOPIC_LEN, mqttDeviceTopic, "c");
mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263)
snprintf_P(subuf, sizeof(subuf)-1, sTopicFormat, MQTT_MAX_TOPIC_LEN, mqttDeviceTopic, "status");
mqtt->publish(subuf, 0, true, "online"); // retain message for a LWT
// TODO: use a DynamicBufferList. Requires a list-read-capable MQTT client API.
DynamicBuffer buf(1024);
bufferPrint pbuf(buf.data(), buf.size());
XML_response(pbuf);
strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1);
strcat_P(subuf, PSTR("/v"));
snprintf_P(subuf, sizeof(subuf)-1, sTopicFormat, MQTT_MAX_TOPIC_LEN, mqttDeviceTopic, "v");
mqtt->publish(subuf, 0, retainMqttMsg, buf.data(), pbuf.size()); // optionally retain message (#2263)
#endif
}
@@ -213,14 +213,26 @@ bool initMqtt()
{
mqtt->setServer(mqttIP, mqttPort);
} else {
mqtt->setServer(mqttServer, mqttPort);
#ifdef ARDUINO_ARCH_ESP32
String mqttMDNS = mqttServer;
mqttMDNS.toLowerCase(); // make sure we have a lowercase hostname
int pos = mqttMDNS.indexOf(F(".local"));
if (pos > 0) mqttMDNS.remove(pos); // remove .local domain if present (and anything following it)
if (strlen(cmDNS) > 0 && mqttMDNS.length() > 0 && mqttMDNS.indexOf('.') < 0) { // if mDNS is enabled and server does not have domain
mqttIP = MDNS.queryHost(mqttMDNS.c_str());
if (mqttIP != IPAddress()) // if MDNS resolved the hostname
mqtt->setServer(mqttIP, mqttPort);
else
mqtt->setServer(mqttServer, mqttPort);
} else
#endif
mqtt->setServer(mqttServer, mqttPort);
}
mqtt->setClientId(mqttClientID);
if (mqttUser[0] && mqttPass[0]) mqtt->setCredentials(mqttUser, mqttPass);
#ifndef USERMOD_SMARTNEST
strlcpy(mqttStatusTopic, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1);
strcat_P(mqttStatusTopic, PSTR("/status"));
snprintf_P(mqttStatusTopic, sizeof(mqttStatusTopic)-1, sTopicFormat, MQTT_MAX_TOPIC_LEN, mqttDeviceTopic, "status");
mqtt->setWill(mqttStatusTopic, 0, true, "offline"); // LWT message
#endif
mqtt->setKeepAlive(MQTT_KEEP_ALIVE_TIME);

View File

@@ -141,6 +141,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
unsigned colorOrder, type, skip, awmode, channelSwap, maPerLed;
unsigned length, start, maMax;
uint8_t pins[5] = {255, 255, 255, 255, 255};
String text;
// this will set global ABL max current used when per-port ABL is not used
unsigned ablMilliampsMax = request->arg(F("MA")).toInt();
@@ -174,6 +175,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
char sp[4] = "SP"; sp[2] = offset+s; sp[3] = 0; //bus clock speed (DotStar & PWM)
char la[4] = "LA"; la[2] = offset+s; la[3] = 0; //LED mA
char ma[4] = "MA"; ma[2] = offset+s; ma[3] = 0; //max mA
char hs[4] = "HS"; hs[2] = offset+s; hs[3] = 0; //hostname (for network types, custom text for others)
if (!request->hasArg(lp)) {
DEBUG_PRINTF_P(PSTR("# of buses: %d\n"), s+1);
break;
@@ -224,9 +226,10 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
maMax = request->arg(ma).toInt() * request->hasArg(F("PPL")); // if PP-ABL is disabled maMax (per bus) must be 0
}
type |= request->hasArg(rf) << 7; // off refresh override
text = request->arg(hs).substring(0,31);
// actual finalization is done in WLED::loop() (removing old busses and adding new)
// this may happen even before this loop is finished so we do "doInitBusses" after the loop
busConfigs.emplace_back(type, pins, start, length, colorOrder | (channelSwap<<4), request->hasArg(cv), skip, awmode, freq, maPerLed, maMax);
busConfigs.emplace_back(type, pins, start, length, colorOrder | (channelSwap<<4), request->hasArg(cv), skip, awmode, freq, maPerLed, maMax, text);
busesChanged = true;
}
//doInitBusses = busesChanged; // we will do that below to ensure all input data is processed

View File

@@ -73,17 +73,13 @@ void NetworkClass::localMAC(uint8_t* MAC)
bool NetworkClass::isConnected()
{
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
return (WiFi.localIP()[0] != 0 && WiFi.status() == WL_CONNECTED) || ETH.localIP()[0] != 0;
#else
return (WiFi.localIP()[0] != 0 && WiFi.status() == WL_CONNECTED);
#endif
return (WiFi.localIP()[0] != 0 && WiFi.status() == WL_CONNECTED) || isEthernet();
}
bool NetworkClass::isEthernet()
{
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
return (ETH.localIP()[0] != 0);
return (ETH.localIP()[0] != 0) && ETH.linkUp();
#endif
return false;
}

View File

@@ -619,7 +619,8 @@ int32_t hw_random(int32_t lowerlimit, int32_t upperlimit) {
return hw_random(diff) + lowerlimit;
}
#ifndef ESP8266
#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;
@@ -640,6 +641,14 @@ void *p_realloc(void *ptr, size_t size) {
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;
@@ -654,7 +663,7 @@ 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 (size > MIN_HEAP_SIZE) std::swap(caps1, caps2); // prefer PSRAM for large alloactions
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);
@@ -664,12 +673,20 @@ 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 (size > MIN_HEAP_SIZE) std::swap(caps1, caps2); // prefer PSRAM for large alloactions
if (heap_caps_get_largest_free_block(caps1) < 3*MIN_HEAP_SIZE && size > MIN_HEAP_SIZE) std::swap(caps1, caps2); // prefer PSRAM for large alloactions & when DRAM is low
return heap_caps_realloc_prefer(ptr, size, 2, caps1, caps2); // otherwise prefer DRAM
}
return heap_caps_realloc(ptr, size, caps1);
}
// realloc with malloc fallback, original buffer is freed if realloc fails but not copied!
void *d_realloc_malloc(void *ptr, size_t size) {
void *newbuf = d_realloc(ptr, size); // try realloc first
if (newbuf) return newbuf; // realloc successful
d_free(ptr); // free old buffer if realloc failed
return d_malloc(size); // fallback to malloc
}
void *d_calloc(size_t count, size_t size) {
int caps1 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
int caps2 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
@@ -679,6 +696,14 @@ void *d_calloc(size_t count, size_t size) {
}
return heap_caps_calloc(count, size, caps1);
}
#else // ESP8266 & ESP32-C3
// realloc with malloc fallback, original buffer is freed if realloc fails but not copied!
void *realloc_malloc(void *ptr, size_t size) {
void *newbuf = realloc(ptr, size); // try realloc first
if (newbuf) return newbuf; // realloc successful
free(ptr); // free old buffer if realloc failed
return malloc(size); // fallback to malloc
}
#endif
/*

View File

@@ -1,7 +1,10 @@
#define WLED_DEFINE_GLOBAL_VARS //only in one source file, wled.cpp!
#include "wled.h"
#include "wled_ethernet.h"
#include <Arduino.h>
#ifdef WLED_ENABLE_AOTA
#define NO_OTA_PORT
#include <ArduinoOTA.h>
#endif
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DISABLE_BROWNOUT_DET)
#include "soc/soc.h"
@@ -105,8 +108,8 @@ void WLED::loop()
if (!realtimeMode || realtimeOverride || (realtimeMode && useMainSegmentOnly)) // block stuff if WARLS/Adalight is enabled
{
if (apActive) dnsServer.processNextRequest();
#ifndef WLED_DISABLE_OTA
if (WLED_CONNECTED && aOtaEnabled && !otaLock && correctPIN) ArduinoOTA.handle();
#ifdef WLED_ENABLE_AOTA
if (Network.isConnected() && aOtaEnabled && !otaLock && correctPIN) ArduinoOTA.handle();
#endif
handleNightlight();
yield();
@@ -471,7 +474,7 @@ void WLED::setup()
if (mqttClientID[0] == 0) sprintf_P(mqttClientID, PSTR("WLED-%*s"), 6, escapedMac.c_str() + 6);
#endif
#ifndef WLED_DISABLE_OTA
#ifdef WLED_ENABLE_AOTA
if (aOtaEnabled) {
ArduinoOTA.onStart([]() {
#ifdef ESP8266
@@ -711,9 +714,8 @@ void WLED::initInterfaces()
alexaInit();
#endif
#ifndef WLED_DISABLE_OTA
if (aOtaEnabled)
ArduinoOTA.begin();
#ifdef WLED_ENABLE_AOTA
if (aOtaEnabled) ArduinoOTA.begin();
#endif
// Set up mDNS responder:
@@ -784,7 +786,7 @@ void WLED::handleConnection()
if (stac != stacO) {
stacO = stac;
DEBUG_PRINTF_P(PSTR("Connected AP clients: %d\n"), (int)stac);
if (!WLED_CONNECTED && wifiConfigured) { // trying to connect, but not connected
if (!Network.isConnected() && wifiConfigured) { // trying to connect, but not connected
if (stac)
WiFi.disconnect(); // disable search so that AP can work
else
@@ -859,7 +861,7 @@ void WLED::handleConnection()
}
// If status LED pin is allocated for other uses, does nothing
// else blink at 1Hz when WLED_CONNECTED is false (no WiFi, ?? no Ethernet ??)
// else blink at 1Hz when Network.isConnected() is false (no WiFi, ?? no Ethernet ??)
// else blink at 2Hz when MQTT is enabled but not connected
// else turn the status LED off
#if defined(STATUSLED)
@@ -873,7 +875,7 @@ void WLED::handleStatusLED()
}
#endif
if (WLED_CONNECTED) {
if (Network.isConnected()) {
c = RGBW32(0,255,0,0);
ledStatusType = 2;
} else if (WLED_MQTT_CONNECTED) {

View File

@@ -21,6 +21,12 @@
// You are required to disable over-the-air updates:
//#define WLED_DISABLE_OTA // saves 14kb
#ifdef WLED_ENABLE_AOTA
#if defined(WLED_DISABLE_OTA)
#warning WLED_DISABLE_OTA was defined but it will be ignored due to WLED_ENABLE_AOTA.
#endif
#undef WLED_DISABLE_OTA
#endif
// You can choose some of these features to disable:
//#define WLED_DISABLE_ALEXA // saves 11kb
@@ -121,10 +127,6 @@
#endif
#include <WiFiUdp.h>
#include <DNSServer.h>
#ifndef WLED_DISABLE_OTA
#define NO_OTA_PORT
#include <ArduinoOTA.h>
#endif
#include <SPIFFSEditor.h>
#include "src/dependencies/time/TimeLib.h"
#include "src/dependencies/timezone/Timezone.h"
@@ -585,7 +587,7 @@ WLED_GLOBAL bool otaLock _INIT(true); // prevents OTA firmware update
WLED_GLOBAL bool otaLock _INIT(false); // prevents OTA firmware updates without password. ALWAYS enable if system exposed to any public networks
#endif
WLED_GLOBAL bool wifiLock _INIT(false); // prevents access to WiFi settings when OTA lock is enabled
#ifndef WLED_DISABLE_OTA
#ifdef WLED_ENABLE_AOTA
WLED_GLOBAL bool aOtaEnabled _INIT(true); // ArduinoOTA allows easy updates directly from the IDE. Careful, it does not auto-disable when OTA lock is on
#else
WLED_GLOBAL bool aOtaEnabled _INIT(false); // ArduinoOTA allows easy updates directly from the IDE. Careful, it does not auto-disable when OTA lock is on
@@ -1040,11 +1042,7 @@ WLED_GLOBAL volatile uint8_t jsonBufferLock _INIT(0);
WLED_GLOBAL unsigned loops _INIT(0);
#endif
#ifdef ARDUINO_ARCH_ESP32
#define WLED_CONNECTED (WiFi.status() == WL_CONNECTED || ETH.localIP()[0] != 0)
#else
#define WLED_CONNECTED (WiFi.status() == WL_CONNECTED)
#endif
#define WLED_CONNECTED (Network.isConnected())
#ifndef WLED_AP_SSID_UNIQUE
#define WLED_SET_AP_SSID() do { \

View File

@@ -1,9 +1,11 @@
#include "wled.h"
#ifdef ESP8266
#include <Updater.h>
#else
#include <Update.h>
#ifndef WLED_DISABLE_OTA
#ifdef ESP8266
#include <Updater.h>
#else
#include <Update.h>
#endif
#endif
#include "html_ui.h"
#include "html_settings.h"
@@ -387,6 +389,7 @@ void initServer()
createEditHandler(correctPIN);
static const char _update[] PROGMEM = "/update";
#ifndef WLED_DISABLE_OTA
//init ota page
server.on(_update, HTTP_GET, [](AsyncWebServerRequest *request){
if (otaLock) {
@@ -446,14 +449,17 @@ void initServer()
}
}
});
#else
const auto notSupported = [](AsyncWebServerRequest *request){
serveMessage(request, 501, FPSTR(s_notimplemented), F("This build does not support OTA update."), 254);
};
server.on(_update, HTTP_GET, notSupported);
server.on(_update, HTTP_POST, notSupported, [](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool isFinal){});
#endif
#ifdef WLED_ENABLE_DMX
server.on(F("/dmxmap"), HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, FPSTR(CONTENT_TYPE_HTML), PAGE_dmxmap , dmxProcessor);
});
#else
server.on(F("/dmxmap"), HTTP_GET, [](AsyncWebServerRequest *request){
serveMessage(request, 501, FPSTR(s_notimplemented), F("DMX support is not enabled in this build."), 254);
request->send_P(200, FPSTR(CONTENT_TYPE_HTML), PAGE_dmxmap, dmxProcessor);
});
#endif
@@ -657,6 +663,7 @@ void serveSettings(AsyncWebServerRequest* request, bool post) {
case SUBPAGE_DMX : content = PAGE_settings_dmx; len = PAGE_settings_dmx_length; break;
#endif
case SUBPAGE_UM : content = PAGE_settings_um; len = PAGE_settings_um_length; break;
#ifndef WLED_DISABLE_OTA
case SUBPAGE_UPDATE : content = PAGE_update; len = PAGE_update_length;
#ifdef ARDUINO_ARCH_ESP32
if (request->hasArg(F("revert")) && inLocalSubnet(request->client()->remoteIP()) && Update.canRollBack()) {
@@ -670,6 +677,7 @@ void serveSettings(AsyncWebServerRequest* request, bool post) {
}
#endif
break;
#endif
#ifndef WLED_DISABLE_2D
case SUBPAGE_2D : content = PAGE_settings_2D; len = PAGE_settings_2D_length; break;
#endif

View File

@@ -312,6 +312,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
char sp[4] = "SP"; sp[2] = offset+s; sp[3] = 0; //bus clock speed
char la[4] = "LA"; la[2] = offset+s; la[3] = 0; //LED current
char ma[4] = "MA"; ma[2] = offset+s; ma[3] = 0; //max per-port PSU current
char hs[4] = "HS"; hs[2] = offset+s; hs[3] = 0; //hostname (for network types, custom text for others)
settingsScript.print(F("addLEDs(1);"));
uint8_t pins[5];
int nPins = bus->getPins(pins);
@@ -351,6 +352,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
printSetFormValue(settingsScript,sp,speed);
printSetFormValue(settingsScript,la,bus->getLEDCurrent());
printSetFormValue(settingsScript,ma,bus->getMaxCurrent());
printSetFormValue(settingsScript,hs,bus->getCustomText().c_str());
sumMa += bus->getMaxCurrent();
}
printSetFormValue(settingsScript,PSTR("MA"),BusManager::ablMilliampsMax() ? BusManager::ablMilliampsMax() : sumMa);
@@ -599,8 +601,11 @@ void getSettingsJS(byte subPage, Print& settingsScript)
snprintf_P(tmp_buf,sizeof(tmp_buf),PSTR("WLED %s (build %d)"),versionString,VERSION);
printSetClassElementHTML(settingsScript,PSTR("sip"),0,tmp_buf);
settingsScript.printf_P(PSTR("sd=\"%s\";"), serverDescription);
#ifdef WLED_DISABLE_OTA
//hide settings if not compiled
#ifdef WLED_DISABLE_OTA
settingsScript.print(F("toggle('OTA');")); // hide update section
#endif
#ifndef WLED_ENABLE_AOTA
settingsScript.print(F("toggle('aOTA');")); // hide ArduinoOTA checkbox
#endif
}