Merge branch 'main' into multibutton
This commit is contained in:
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
@@ -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
286
tools/wled-tools
Executable 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
|
||||
@@ -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
11
wled00/FX.h
Executable file → Normal 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)
|
||||
|
||||
@@ -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."));
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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 ¶llelspeed, 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 ¶llelspeed, 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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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", ()=>{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -53,13 +53,13 @@
|
||||
Factory reset: <input type="checkbox" name="RS"><br>
|
||||
All settings and presets will be erased.<br><br>
|
||||
<div class="warn">⚠ 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">⚠ 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">⚠ Restoring presets/configuration will OVERWRITE your current presets/configuration.<br>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
/*
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 { \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user