Compare commits

..

33 Commits

Author SHA1 Message Date
Will Tatam
9f3e53a516 disable 8266 builds for now 2025-11-08 16:39:31 +00:00
Will Tatam
b51e80f5dd Safety check: ensure multiWiFi is never empty to prevent crashes during serialization 2025-11-08 16:28:19 +00:00
Will Tatam
d632cf8e8c improve esp32_dev env 2025-11-08 15:02:33 +00:00
Will Tatam
7c8b8fd0f3 NeoPixelBus CORE3 2025-11-08 15:02:09 +00:00
Will Tatam
36eaca6cb2 NeoEsp32RmtHI does not support V5 2025-11-08 13:24:39 +00:00
Will Tatam
0653854904 NeoEsp32RmtHI does not support V5 2025-11-08 13:03:55 +00:00
Will Tatam
51e1f4fc04 NeoEsp32RmtHI does not support V5 2025-11-08 12:49:52 +00:00
Will Tatam
2af23fc0cd AsyncTCP 3.4.7 does not support V5 2025-11-08 12:49:27 +00:00
Will Tatam
ce84a57a0d NeoEsp32RmtHI does not support V5 2025-11-08 12:44:26 +00:00
Will Tatam
8cad34e14a Merge branch 'main' into V5 2025-11-08 12:19:01 +00:00
Will Tatam
93821efb90 disable usermods build for now 2025-11-08 12:11:48 +00:00
Will Tatam
c5e2ec72bd WLED_DISABLE_INFRARED for 8266 2025-11-08 12:08:50 +00:00
Will Tatam
86679edd1f ESP-IDF fix for PWM_fan.cpp 2025-11-08 12:07:49 +00:00
Will Tatam
7a0325f88f WLED_DISABLE_INFRARED for 8266 2025-11-08 12:02:25 +00:00
Will Tatam
44a483f454 WLED_DISABLE_INFRARED for 8266 2025-11-08 11:55:01 +00:00
Will Tatam
41878f01f9 WLED_DISABLE_INFRARED for 8266 2025-11-08 11:51:08 +00:00
Will Tatam
906d4560a7 fix for pin usage logger 2025-11-08 11:43:38 +00:00
Will Tatam
5205110960 mbedtls_sha1_shim.cpp not required for 8266 2025-11-08 11:40:00 +00:00
Will Tatam
2f8882ee56 WLED_DISABLE_INFRARED for 8266 2025-11-08 11:39:02 +00:00
Will Tatam
d4bafec0f3 WLED_DISABLE_MQTT for 8266 2025-11-08 11:27:33 +00:00
Will Tatam
446c04380e fix for esp32_eth 2025-11-08 11:20:03 +00:00
Will Tatam
7daada1f3e fix for esp32_eth 2025-11-08 11:18:25 +00:00
Will Tatam
68dff2d392 mbedtls_sha1 workaround 2025-11-08 10:53:41 +00:00
Will Tatam
ec22c50813 Update DMX Input for latest esp_dmx 2025-11-08 10:38:19 +00:00
Will Tatam
81c92257da Update PlatformIO 2025-08-16 12:45:36 +01:00
Will Tatam
e4815d1ff5 Update PlatformIO 2025-08-16 12:36:41 +01:00
Will Tatam
bc19133e73 Update PlatformIO 2025-08-16 12:33:25 +01:00
Will Tatam
03a9d9e56f ledc changes for versions 2.X (based on ESP-IDF 4.4) to version 3.0 (based on ESP-IDF 5.1) - https://docs.espressif.com/projects/arduino-esp32/en/latest/migration_guides/2.x_to_3.0.html 2025-08-16 12:05:41 +01:00
Will Tatam
2c0259f214 Rename Network to WLEDNetwork to prevent clash 2025-08-16 11:57:50 +01:00
Will Tatam
1de8c2e79b Remove local copy of MQTT and disable for now in build 2025-08-16 11:57:20 +01:00
Will Tatam
07ab6aa3ea Rename Network to WLEDNetwork to prevent clash 2025-08-14 18:59:06 +01:00
Will Tatam
ba2b182bb3 Update platformio.ini for V5 2025-08-14 18:26:12 +01:00
Will Tatam
965e794094 Rename Network to WLEDNetwork to prevent clash 2025-08-14 18:25:39 +01:00
45 changed files with 1100 additions and 2676 deletions

View File

@@ -1,3 +1,4 @@
#if false
/*-------------------------------------------------------------------------
NeoPixel driver for ESP32 RMTs using High-priority Interrupt
@@ -467,3 +468,5 @@ typedef NeoEsp32RmtHI7Ws2805InvertedMethod NeoEsp32RmtHI7Ws2814InvertedMethod;
#endif // !defined(CONFIG_IDF_TARGET_ESP32C3)
#endif
#endif // NEOE_SP32_RMT_HI_METHODS_H

View File

@@ -1,3 +1,5 @@
#if false
/*-------------------------------------------------------------------------
NeoPixel library helper functions for Esp32.
@@ -504,4 +506,6 @@ esp_err_t NeoEsp32RmtHiMethodDriver::WaitForTxDone(rmt_channel_t channel, TickTy
return rv;
}
#endif
#endif
#endif // ARDUINO_ARCH_ESP32

View File

@@ -2,7 +2,6 @@ Import('env')
import os
import shutil
import gzip
import json
OUTPUT_DIR = "build_output{}".format(os.path.sep)
#OUTPUT_DIR = os.path.join("build_output")
@@ -23,8 +22,7 @@ def create_release(source):
release_name_def = _get_cpp_define_value(env, "WLED_RELEASE_NAME")
if release_name_def:
release_name = release_name_def.replace("\\\"", "")
with open("package.json", "r") as package:
version = json.load(package)["version"]
version = _get_cpp_define_value(env, "WLED_VERSION")
release_file = os.path.join(OUTPUT_DIR, "release", f"WLED_{version}_{release_name}.bin")
release_gz_file = release_file + ".gz"
print(f"Copying {source} to {release_file}")

View File

@@ -1,6 +1,5 @@
Import('env')
import subprocess
import json
import re
def get_github_repo():
@@ -43,7 +42,7 @@ def get_github_repo():
# Check if it's a GitHub URL
if 'github.com' not in remote_url.lower():
return None
return 'unknown'
# Parse GitHub URL patterns:
# https://github.com/owner/repo.git
@@ -64,53 +63,17 @@ def get_github_repo():
if ssh_match:
return ssh_match.group(1)
return None
return 'unknown'
except FileNotFoundError:
# Git CLI is not installed or not in PATH
return None
return 'unknown'
except subprocess.CalledProcessError:
# Git command failed (e.g., not a git repo, no remote, etc.)
return None
return 'unknown'
except Exception:
# Any other unexpected error
return None
return 'unknown'
# WLED version is managed by package.json; this is picked up in several places
# - It's integrated in to the UI code
# - Here, for wled_metadata.cpp
# - The output_bins script
# We always take it from package.json to ensure consistency
with open("package.json", "r") as package:
WLED_VERSION = json.load(package)["version"]
def has_def(cppdefs, name):
""" Returns true if a given name is set in a CPPDEFINES collection """
for f in cppdefs:
if isinstance(f, tuple):
f = f[0]
if f == name:
return True
return False
def add_wled_metadata_flags(env, node):
cdefs = env["CPPDEFINES"].copy()
if not has_def(cdefs, "WLED_REPO"):
repo = get_github_repo()
if repo:
cdefs.append(("WLED_REPO", f"\\\"{repo}\\\""))
cdefs.append(("WLED_VERSION", WLED_VERSION))
# This transforms the node in to a Builder; it cannot be modified again
return env.Object(
node,
CPPDEFINES=cdefs
)
env.AddBuildMiddleware(
add_wled_metadata_flags,
"*/wled_metadata.cpp"
)
repo = get_github_repo()
env.Append(BUILD_FLAGS=[f'-DWLED_REPO=\\"{repo}\\"'])

View File

@@ -0,0 +1,8 @@
Import('env')
import json
PACKAGE_FILE = "package.json"
with open(PACKAGE_FILE, "r") as package:
version = json.load(package)["version"]
env.Append(BUILD_FLAGS=[f"-DWLED_VERSION={version}"])

View File

@@ -10,26 +10,26 @@
# ------------------------------------------------------------------------------
# CI/release binaries
default_envs = nodemcuv2
esp8266_2m
esp01_1m_full
nodemcuv2_160
esp8266_2m_160
esp01_1m_full_160
nodemcuv2_compat
esp8266_2m_compat
esp01_1m_full_compat
default_envs =
; nodemcuv2
; esp8266_2m
; esp01_1m_full
; nodemcuv2_160
; esp8266_2m_160
; esp01_1m_full_160
; nodemcuv2_compat
; esp8266_2m_compat
; esp01_1m_full_compat
esp32dev
esp32dev_debug
esp32_eth
esp32_wrover
lolin_s2_mini
; lolin_s2_mini ;; TODO: disabled NeoEsp32RmtMethodIsr
esp32c3dev
esp32c3dev_qio
esp32s3dev_16MB_opi
esp32s3dev_8MB_opi
esp32s3_4M_qspi
usermods
; esp32s3dev_16MB_opi ;; TODO: disabled NeoEsp32RmtMethodIsr
; esp32s3dev_8MB_opi ;; TODO: disabled NeoEsp32RmtMethodIsr
;esp32s3_4M_qspi ;; TODO: disabled NeoEsp32RmtMethodIsr
; usermods
src_dir = ./wled00
data_dir = ./wled00/data
@@ -119,6 +119,7 @@ build_flags =
-D DECODE_SAMSUNG=true
-D DECODE_LG=true
-DWLED_USE_MY_CONFIG
-D WLED_USE_SHARED_RMT ;; Use standard RMT method instead of incompatible NeoEsp32RmtHI - until updated for V5
build_unflags =
@@ -127,9 +128,12 @@ ldscript_2m512k = eagle.flash.2m512.ld
ldscript_2m1m = eagle.flash.2m1m.ld
ldscript_4m1m = eagle.flash.4m1m.ld
default_usermods = ;; TODO: add back audioreactive once V5 compatible
[scripts_defaults]
extra_scripts =
pre:pio-scripts/set_metadata.py
pre:pio-scripts/set_version.py
pre:pio-scripts/set_repo.py
post:pio-scripts/output_bins.py
post:pio-scripts/strip-floats.py
pre:pio-scripts/user_config_copy.py
@@ -157,9 +161,9 @@ upload_speed = 115200
# ------------------------------------------------------------------------------
lib_compat_mode = strict
lib_deps =
fastled/FastLED @ 3.6.0
IRremoteESP8266 @ 2.8.2
makuna/NeoPixelBus @ 2.8.3
fastled/FastLED @ 3.10.1
; IRremoteESP8266 @ 2.8.2
https://github.com/netmindz/NeoPixelBus.git#2f05279a4a9f56875fb85482e6ec4e17078accc3 ;; CORE3 with log fix
https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.4.2
marvinroger/AsyncMqttClient @ 0.9.0
# for I2C interface
@@ -212,7 +216,8 @@ build_flags =
-D PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48 ;; in case of linker errors like "section `.text1' will not fit in region `iram1_0_seg'"
; -D PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48_SECHEAP_SHARED ;; (experimental) adds some extra heap, but may cause slowdown
-D NON32XFER_HANDLER ;; ask forgiveness for PROGMEM misuse
-D WLED_DISABLE_MQTT ;; TODO: remove once we have updated library for V5
-D WLED_DISABLE_INFRARED ;; TODO: remove once we have updated for V5
lib_deps =
#https://github.com/lorol/LITTLEFS.git
ESPAsyncTCP @ 1.2.2
@@ -232,6 +237,7 @@ build_flags_compat =
-DVTABLES_IN_FLASH
-DMIMETYPE_MINIMAL
-DWLED_SAVE_IRAM ;; needed to prevent linker error
-D WLED_DISABLE_MQTT ;; TODO: remove once we have updated library for V5
;; this platform version was used for WLED 0.14.0
platform_compat = espressif8266@4.2.0
@@ -253,7 +259,7 @@ lib_deps_compat =
[esp32_all_variants]
lib_deps =
esp32async/AsyncTCP @ 3.4.7
esp32async/AsyncTCP @ 3.4.6
bitbank2/AnimatedGIF@^1.4.7
https://github.com/Aircoookie/GifDecoder#bc3af18
build_flags =
@@ -262,11 +268,11 @@ build_flags =
-D WLED_ENABLE_GIF
[esp32]
platform = ${esp32_idf_V4.platform}
platform = ${esp32_idf_V5.platform}
platform_packages =
build_unflags = ${common.build_unflags}
build_flags = ${esp32_idf_V4.build_flags}
lib_deps = ${esp32_idf_V4.lib_deps}
build_flags = ${esp32_idf_V5.build_flags}
lib_deps = ${esp32_idf_V5.lib_deps}
tiny_partitions = tools/WLED_ESP32_2MB_noOTA.csv
default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
@@ -281,31 +287,28 @@ AR_build_flags = ;; -fsingle-precision-constant ;; forces ArduinoFFT to use floa
AR_lib_deps = ;; for pre-usermod-library platformio_override compatibility
[esp32_idf_V4]
;; build environment for ESP32 using ESP-IDF 4.4.x / arduino-esp32 v2.0.5
;; *** important: build flags from esp32_idf_V4 are inherited by _all_ esp32-based MCUs: esp32, esp32s2, esp32s3, esp32c3
;;
;; please note that you can NOT update existing ESP32 installs with a "V4" build. Also updating by OTA will not work properly.
;; You need to completely erase your device (esptool erase_flash) first, then install the "V4" build from VSCode+platformio.
;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them)
platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.06.02/platform-espressif32.zip ;; Tasmota Arduino Core 2.0.9 with IPv6 support, based on IDF 4.4.4
platform_packages =
[esp32_idf_V5]
;; build environment for ESP32 using ESP-IDF 5.3.3 / arduino-esp32 v3.1.3
platform = https://github.com/tasmota/platform-espressif32/releases/download/2025.08.30/platform-espressif32-2025.08.30.zip ;; Platform 2025.08.30 Tasmota Arduino Core 3.1.3.250808 based on IDF 5.3.3.250801
build_unflags = ${common.build_unflags}
build_flags = -g
-Wshadow=compatible-local ;; emit warning in case a local variable "shadows" another local one
-DARDUINO_ARCH_ESP32 -DESP32
${esp32_all_variants.build_flags}
-D WLED_ENABLE_DMX_INPUT
-D WLED_DISABLE_INFRARED ;; TODO: remove once we have updated library for V5
-D WLED_DISABLE_MQTT ;; TODO: remove once we have updated library for V5
-D WLED_ENABLE_DMX_INPUT
-D WLED_USE_SHARED_RMT ;; Use standard RMT method instead of incompatible NeoEsp32RmtHI - until updated for V5
lib_deps =
${esp32_all_variants.lib_deps}
https://github.com/someweisguy/esp_dmx.git#47db25d
https://github.com/netmindz/esp_dmx/#esp-idf-v5-fixes
${env.lib_deps}
lib_ignore =
NeoESP32RmtHI
[esp32s2]
;; generic definitions for all ESP32-S2 boards
platform = ${esp32_idf_V4.platform}
platform_packages = ${esp32_idf_V4.platform_packages}
platform = ${esp32_idf_V5.platform}
build_unflags = ${common.build_unflags}
build_flags = -g
-DARDUINO_ARCH_ESP32
@@ -316,15 +319,14 @@ build_flags = -g
-DARDUINO_USB_MODE=0 ;; this flag is mandatory for ESP32-S2 !
;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry:
;; ARDUINO_USB_CDC_ON_BOOT
${esp32_idf_V4.build_flags}
${esp32_idf_V5.build_flags}
lib_deps =
${esp32_idf_V4.lib_deps}
${esp32_idf_V5.lib_deps}
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs
[esp32c3]
;; generic definitions for all ESP32-C3 boards
platform = ${esp32_idf_V4.platform}
platform_packages = ${esp32_idf_V4.platform_packages}
platform = ${esp32_idf_V5.platform}
build_unflags = ${common.build_unflags}
build_flags = -g
-DARDUINO_ARCH_ESP32
@@ -334,16 +336,15 @@ build_flags = -g
-DARDUINO_USB_MODE=1 ;; this flag is mandatory for ESP32-C3
;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry:
;; ARDUINO_USB_CDC_ON_BOOT
${esp32_idf_V4.build_flags}
${esp32_idf_V5.build_flags}
lib_deps =
${esp32_idf_V4.lib_deps}
${esp32_idf_V5.lib_deps}
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs
board_build.flash_mode = qio
[esp32s3]
;; generic definitions for all ESP32-S3 boards
platform = ${esp32_idf_V4.platform}
platform_packages = ${esp32_idf_V4.platform_packages}
platform = ${esp32_idf_V5.platform}
build_unflags = ${common.build_unflags}
build_flags = -g
-DESP32
@@ -354,9 +355,9 @@ build_flags = -g
-DCO
;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry:
;; ARDUINO_USB_MODE, ARDUINO_USB_CDC_ON_BOOT
${esp32_idf_V4.build_flags}
${esp32_idf_V5.build_flags}
lib_deps =
${esp32_idf_V4.lib_deps}
${esp32_idf_V5.lib_deps}
board_build.partitions = ${esp32.large_partitions} ;; default partioning for 8MB flash - can be overridden in build envs
@@ -382,6 +383,8 @@ platform = ${esp8266.platform_compat}
platform_packages = ${esp8266.platform_packages_compat}
build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP8266_compat\" #-DWLED_DISABLE_2D
-D WLED_DISABLE_PARTICLESYSTEM2D
-D WLED_DISABLE_INFRARED ;; TODO: remove once we have updated for V5
;; lib_deps = ${esp8266.lib_deps_compat} ;; experimental - use older NeoPixelBus 2.7.9
[env:nodemcuv2_160]
@@ -389,7 +392,7 @@ extends = env:nodemcuv2
board_build.f_cpu = 160000000L
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266_160\" #-DWLED_DISABLE_2D
-D WLED_DISABLE_PARTICLESYSTEM2D
custom_usermods = audioreactive
custom_usermods = ${common.default_usermods}
[env:esp8266_2m]
board = esp_wroom_02
@@ -410,6 +413,8 @@ platform_packages = ${esp8266.platform_packages_compat}
build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP02_compat\" #-DWLED_DISABLE_2D
-D WLED_DISABLE_PARTICLESYSTEM1D
-D WLED_DISABLE_PARTICLESYSTEM2D
-D WLED_DISABLE_INFRARED ;; TODO: remove once we have updated for V5
[env:esp8266_2m_160]
extends = env:esp8266_2m
@@ -417,7 +422,7 @@ board_build.f_cpu = 160000000L
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP02_160\"
-D WLED_DISABLE_PARTICLESYSTEM1D
-D WLED_DISABLE_PARTICLESYSTEM2D
custom_usermods = audioreactive
custom_usermods = ${common.default_usermods}
[env:esp01_1m_full]
board = esp01_1m
@@ -429,6 +434,8 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=
; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM
-D WLED_DISABLE_PARTICLESYSTEM1D
-D WLED_DISABLE_PARTICLESYSTEM2D
-D WLED_DISABLE_INFRARED ;; TODO: remove once we have updated for V5
lib_deps = ${esp8266.lib_deps}
[env:esp01_1m_full_compat]
@@ -439,6 +446,8 @@ platform_packages = ${esp8266.platform_packages_compat}
build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP01_compat\" -D WLED_DISABLE_OTA #-DWLED_DISABLE_2D
-D WLED_DISABLE_PARTICLESYSTEM1D
-D WLED_DISABLE_PARTICLESYSTEM2D
-D WLED_DISABLE_INFRARED ;; TODO: remove once we have updated for V5
-D WLED_DISABLE_INFRARED ;; TODO: remove once we have updated for V5
[env:esp01_1m_full_160]
extends = env:esp01_1m_full
@@ -447,37 +456,37 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=
; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM
-D WLED_DISABLE_PARTICLESYSTEM1D
-D WLED_DISABLE_PARTICLESYSTEM2D
custom_usermods = audioreactive
custom_usermods = ${common.default_usermods}
[env:esp32dev]
board = esp32dev
platform = ${esp32_idf_V4.platform}
platform_packages = ${esp32_idf_V4.platform_packages}
platform = ${esp32_idf_V5.platform}
build_unflags = ${common.build_unflags}
custom_usermods = audioreactive
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32\" #-D WLED_DISABLE_BROWNOUT_DET
custom_usermods = ${common.default_usermods}
build_flags = ${common.build_flags} ${esp32_idf_V5.build_flags} -D WLED_RELEASE_NAME=\"ESP32\" #-D WLED_DISABLE_BROWNOUT_DET
-DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
lib_deps = ${esp32_idf_V4.lib_deps}
lib_deps = ${esp32_idf_V5.lib_deps}
monitor_filters = esp32_exception_decoder
board_build.partitions = ${esp32.default_partitions}
board_build.flash_mode = dio
[env:esp32dev_debug]
extends = env:esp32dev
build_type = debug
monitor_filters = esp32_exception_decoder
upload_speed = 921600
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags}
build_unflags = -D WLED_RELEASE_NAME=\"ESP32\"
build_flags = ${env:esp32dev.build_flags}
-D WLED_DEBUG
-D WLED_RELEASE_NAME=\"ESP32_DEBUG\"
[env:esp32dev_8M]
board = esp32dev
platform = ${esp32_idf_V4.platform}
platform_packages = ${esp32_idf_V4.platform_packages}
custom_usermods = audioreactive
platform = ${esp32_idf_V5.platform}
custom_usermods = ${common.default_usermods}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_8M\" #-D WLED_DISABLE_BROWNOUT_DET
-DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
lib_deps = ${esp32_idf_V4.lib_deps}
build_flags = ${common.build_flags} ${esp32_idf_V5.build_flags} -D WLED_RELEASE_NAME=\"ESP32_8M\" #-D WLED_DISABLE_BROWNOUT_DET
lib_deps = ${esp32_idf_V5.lib_deps}
monitor_filters = esp32_exception_decoder
board_build.partitions = ${esp32.large_partitions}
board_upload.flash_size = 8MB
@@ -487,13 +496,11 @@ board_build.flash_mode = dio
[env:esp32dev_16M]
board = esp32dev
platform = ${esp32_idf_V4.platform}
platform_packages = ${esp32_idf_V4.platform_packages}
custom_usermods = audioreactive
platform = ${esp32_idf_V5.platform}
custom_usermods = ${common.default_usermods}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_16M\" #-D WLED_DISABLE_BROWNOUT_DET
-DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
lib_deps = ${esp32_idf_V4.lib_deps}
build_flags = ${common.build_flags} ${esp32_idf_V5.build_flags} -D WLED_RELEASE_NAME=\"ESP32_16M\" #-D WLED_DISABLE_BROWNOUT_DET
lib_deps = ${esp32_idf_V5.lib_deps}
monitor_filters = esp32_exception_decoder
board_build.partitions = ${esp32.extreme_partitions}
board_upload.flash_size = 16MB
@@ -503,36 +510,32 @@ board_build.flash_mode = dio
[env:esp32_eth]
board = esp32-poe
platform = ${esp32_idf_V4.platform}
platform_packages = ${esp32_idf_V4.platform_packages}
platform = ${esp32_idf_V5.platform}
upload_speed = 921600
custom_usermods = audioreactive
custom_usermods = ${common.default_usermods}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32_Ethernet\" -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1
-DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
build_flags = ${common.build_flags} ${esp32_idf_V5.build_flags} -D WLED_RELEASE_NAME=\"ESP32_Ethernet\" -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1
; -D WLED_DISABLE_ESPNOW ;; ESP-NOW requires wifi, may crash with ethernet only
lib_deps = ${esp32.lib_deps}
lib_deps = ${esp32_idf_V5.lib_deps}
board_build.partitions = ${esp32.default_partitions}
board_build.flash_mode = dio
[env:esp32_wrover]
extends = esp32_idf_V4
extends = esp32_idf_V5
board = ttgo-t7-v14-mini32
board_build.f_flash = 80000000L
board_build.flash_mode = qio
board_build.partitions = ${esp32.extended_partitions}
custom_usermods = audioreactive
custom_usermods = ${common.default_usermods}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_WROVER\"
-DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
build_flags = ${common.build_flags} ${esp32_idf_V5.build_flags} -D WLED_RELEASE_NAME=\"ESP32_WROVER\"
-DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue ;; Older ESP32 (rev.<3) need a PSRAM fix (increases static RAM used) https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/external-ram.html
-D DATA_PINS=25
lib_deps = ${esp32_idf_V4.lib_deps}
lib_deps = ${esp32_idf_V5.lib_deps}
[env:esp32c3dev]
extends = esp32c3
platform = ${esp32c3.platform}
platform_packages = ${esp32c3.platform_packages}
framework = arduino
board = esp32-c3-devkitm-1
board_build.partitions = ${esp32.default_partitions}
@@ -544,21 +547,14 @@ build_flags = ${common.build_flags} ${esp32c3.build_flags} -D WLED_RELEASE_NAME=
upload_speed = 460800
build_unflags = ${common.build_unflags}
lib_deps = ${esp32c3.lib_deps}
board_build.flash_mode = dio ; safe default, required for OTA updates to 0.16 from older version which used dio (must match the bootloader!)
[env:esp32c3dev_qio]
extends = env:esp32c3dev
build_flags = ${common.build_flags} ${esp32c3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-C3-QIO\"
board_build.flash_mode = qio ; qio is faster and works on almost all boards (some boards may use dio to get 2 extra pins)
[env:esp32s3dev_16MB_opi]
;; ESP32-S3 development board, with 16MB FLASH and >= 8MB PSRAM (memory_type: qio_opi)
board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support
board_build.arduino.memory_type = qio_opi ;; use with PSRAM: 8MB or 16MB
platform = ${esp32s3.platform}
platform_packages = ${esp32s3.platform_packages}
upload_speed = 921600
custom_usermods = audioreactive
custom_usermods = ${common.default_usermods}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_16MB_opi\"
-D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0
@@ -578,9 +574,8 @@ monitor_filters = esp32_exception_decoder
board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support
board_build.arduino.memory_type = qio_opi ;; use with PSRAM: 8MB or 16MB
platform = ${esp32s3.platform}
platform_packages = ${esp32s3.platform_packages}
upload_speed = 921600
custom_usermods = audioreactive
custom_usermods = ${common.default_usermods}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_8MB_opi\"
-D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0
@@ -597,11 +592,10 @@ monitor_filters = esp32_exception_decoder
;; For ESP32-S3 WROOM-2, a.k.a. ESP32-S3 DevKitC-1 v1.1
;; with >= 16MB FLASH and >= 8MB PSRAM (memory_type: opi_opi)
platform = ${esp32s3.platform}
platform_packages = ${esp32s3.platform_packages}
board = esp32s3camlcd ;; this is the only standard board with "opi_opi"
board_build.arduino.memory_type = opi_opi
upload_speed = 921600
custom_usermods = audioreactive
custom_usermods = ${common.default_usermods}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_WROOM-2\"
-D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0
@@ -623,9 +617,8 @@ monitor_filters = esp32_exception_decoder
;; ESP32-S3, with 4MB FLASH and <= 4MB PSRAM (memory_type: qio_qspi)
board = lolin_s3_mini ;; -S3 mini, 4MB flash 2MB PSRAM
platform = ${esp32s3.platform}
platform_packages = ${esp32s3.platform_packages}
upload_speed = 921600
custom_usermods = audioreactive
custom_usermods = ${common.default_usermods}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_4M_qspi\"
-DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
@@ -640,12 +633,11 @@ monitor_filters = esp32_exception_decoder
[env:lolin_s2_mini]
platform = ${esp32s2.platform}
platform_packages = ${esp32s2.platform_packages}
board = lolin_s2_mini
board_build.partitions = ${esp32.default_partitions}
board_build.flash_mode = qio
board_build.f_flash = 80000000L
custom_usermods = audioreactive
custom_usermods = ${common.default_usermods}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S2\"
-DARDUINO_USB_CDC_ON_BOOT=1
@@ -666,12 +658,11 @@ lib_deps = ${esp32s2.lib_deps}
[env:usermods]
board = esp32dev
platform = ${esp32_idf_V4.platform}
platform_packages = ${esp32_idf_V4.platform_packages}
platform = ${esp32_idf_V5.platform}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_USERMODS\"
build_flags = ${common.build_flags} ${esp32_idf_V5.build_flags} -D WLED_RELEASE_NAME=\"ESP32_USERMODS\"
-DTOUCH_CS=9
lib_deps = ${esp32_idf_V4.lib_deps}
lib_deps = ${esp32_idf_V5.lib_deps}
monitor_filters = esp32_exception_decoder
board_build.flash_mode = dio
custom_usermods = * ; Expands to all usermods in usermods folder

View File

@@ -1,20 +1,20 @@
#
# This file is autogenerated by pip-compile with Python 3.11
# This file is autogenerated by pip-compile with Python 3.13
# by the following command:
#
# pip-compile requirements.in
# pip-compile
#
ajsonrpc==1.2.0
# via platformio
anyio==4.8.0
anyio==4.10.0
# via starlette
bottle==0.13.2
bottle==0.13.4
# via platformio
certifi==2025.1.31
certifi==2025.8.3
# via requests
charset-normalizer==3.4.1
charset-normalizer==3.4.3
# via requests
click==8.1.8
click==8.1.7
# via
# platformio
# uvicorn
@@ -30,9 +30,9 @@ idna==3.10
# requests
marshmallow==3.26.1
# via platformio
packaging==24.2
packaging==25.0
# via marshmallow
platformio==6.1.17
platformio==6.1.18
# via -r requirements.in
pyelftools==0.32
# via platformio
@@ -44,15 +44,13 @@ semantic-version==2.10.0
# via platformio
sniffio==1.3.1
# via anyio
starlette==0.45.3
starlette==0.46.2
# via platformio
tabulate==0.9.0
# via platformio
typing-extensions==4.12.2
# via anyio
urllib3==2.5.0
# via requests
uvicorn==0.34.0
uvicorn==0.34.3
# via platformio
wsproto==1.2.0
# via platformio

View File

@@ -26,7 +26,7 @@ const packageJson = require("../package.json");
// Export functions for testing
module.exports = { isFileNewerThan, isAnyFileInFolderNewerThan };
const output = ["wled00/html_ui.h", "wled00/html_pixart.h", "wled00/html_cpal.h", "wled00/html_edit.h", "wled00/html_pxmagic.h", "wled00/html_settings.h", "wled00/html_other.h"]
const output = ["wled00/html_ui.h", "wled00/html_pixart.h", "wled00/html_cpal.h", "wled00/html_pxmagic.h", "wled00/html_settings.h", "wled00/html_other.h"]
// \x1b[34m is blue, \x1b[36m is cyan, \x1b[0m is reset
const wledBanner = `
@@ -246,21 +246,6 @@ writeHtmlGzipped("wled00/data/index.htm", "wled00/html_ui.h", 'index');
writeHtmlGzipped("wled00/data/pixart/pixart.htm", "wled00/html_pixart.h", 'pixart');
//writeHtmlGzipped("wled00/data/cpal/cpal.htm", "wled00/html_cpal.h", 'cpal');
writeHtmlGzipped("wled00/data/pxmagic/pxmagic.htm", "wled00/html_pxmagic.h", 'pxmagic');
//writeHtmlGzipped("wled00/data/edit.htm", "wled00/html_edit.h", 'edit');
writeChunks(
"wled00/data",
[
{
file: "edit.htm",
name: "PAGE_edit",
method: "gzip",
filter: "html-minify"
}
],
"wled00/html_edit.h"
);
writeChunks(
"wled00/data/cpal",
@@ -403,6 +388,12 @@ const char PAGE_dmxmap[] PROGMEM = R"=====()=====";
name: "PAGE_update",
method: "gzip",
filter: "html-minify",
mangle: (str) =>
str
.replace(
/function GetV().*\<\/script\>/gms,
"</script><script src=\"/settings/s.js?p=9\"></script>"
)
},
{
file: "welcome.htm",

View File

@@ -129,11 +129,14 @@ class PWMFanUsermod : public Usermod {
if (pwmChannel == 255) { //no more free LEDC channels
deinitPWMfan(); return;
}
// configure LED PWM functionalitites
// configure LED PWM functionalitites - ESP-IDF 5.x API
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
ledcAttach(pwmPin, 25000, 8); // New API: ledcAttach(pin, freq, resolution)
#else
ledcSetup(pwmChannel, 25000, 8);
// attach the channel to the GPIO to be controlled
ledcAttachPin(pwmPin, pwmChannel);
#endif
#endif
DEBUG_PRINTLN(F("Fan PWM sucessfully initialized."));
}

View File

@@ -196,7 +196,7 @@ class St7789DisplayUsermod : public Usermod {
// Check if values which are shown on display changed from the last time.
if ((((apActive) ? String(apSSID) : WiFi.SSID()) != knownSsid) ||
(knownIp != (apActive ? IPAddress(4, 3, 2, 1) : Network.localIP())) ||
(knownIp != (apActive ? IPAddress(4, 3, 2, 1) : WLEDNetwork.localIP())) ||
(knownBrightness != bri) ||
(knownEffectSpeed != strip.getMainSegment().speed) ||
(knownEffectIntensity != strip.getMainSegment().intensity) ||

View File

@@ -234,11 +234,11 @@ class QuinLEDAnPentaUsermod : public Usermod
bool oledCheckForNetworkChanges()
{
if (lastKnownNetworkConnected != Network.isConnected() || lastKnownIp != Network.localIP()
if (lastKnownNetworkConnected != WLEDNetwork.isConnected() || lastKnownIp != WLEDNetwork.localIP()
|| lastKnownWiFiConnected != WiFi.isConnected() || lastKnownSsid != WiFi.SSID()
|| lastKnownApActive != apActive || lastKnownApSsid != apSSID || lastKnownApPass != apPass || lastKnownApChannel != apChannel) {
lastKnownNetworkConnected = Network.isConnected();
lastKnownIp = Network.localIP();
lastKnownNetworkConnected = WLEDNetwork.isConnected();
lastKnownIp = WLEDNetwork.localIP();
lastKnownWiFiConnected = WiFi.isConnected();
lastKnownSsid = WiFi.SSID();
lastKnownApActive = apActive;

View File

@@ -264,7 +264,7 @@ void FourLineDisplayUsermod::setup() {
// interfaces here
void FourLineDisplayUsermod::connected() {
knownSsid = WiFi.SSID(); //apActive ? apSSID : WiFi.SSID(); //apActive ? WiFi.softAPSSID() :
knownIp = Network.localIP(); //apActive ? IPAddress(4, 3, 2, 1) : Network.localIP();
knownIp = WLEDNetwork.localIP(); //apActive ? IPAddress(4, 3, 2, 1) : WLEDNetwork.localIP();
networkOverlay(PSTR("NETWORK INFO"),7000);
}

View File

@@ -4509,7 +4509,7 @@ uint16_t mode_image(void) {
// Serial.println(status);
// }
}
static const char _data_FX_MODE_IMAGE[] PROGMEM = "Image@!,Blur,;;;12;sx=128,ix=0";
static const char _data_FX_MODE_IMAGE[] PROGMEM = "Image@!,;;;12;sx=128";
/*
Blends random colors across palette
@@ -4669,8 +4669,7 @@ static const char _data_FX_MODE_TV_SIMULATOR[] PROGMEM = "TV Simulator@!,!;;!;01
/*
Aurora effect by @Mazen
improved and converted to integer math by @dedehai
Aurora effect
*/
//CONFIG
@@ -4682,138 +4681,140 @@ static const char _data_FX_MODE_TV_SIMULATOR[] PROGMEM = "TV Simulator@!,!;;!;01
#define W_MAX_SPEED 6 //Higher number, higher speed
#define W_WIDTH_FACTOR 6 //Higher number, smaller waves
// fixed-point math scaling
#define AW_SHIFT 16
#define AW_SCALE (1 << AW_SHIFT) // 65536 representing 1.0
// 32 bytes
//24 bytes
class AuroraWave {
private:
int32_t center; // scaled by AW_SCALE
uint32_t ageFactor_cached; // cached age factor scaled by AW_SCALE
uint16_t ttl;
CRGB basecolor;
float basealpha;
uint16_t age;
uint16_t width;
uint16_t basealpha; // scaled by AW_SCALE
uint16_t speed_factor; // scaled by AW_SCALE
int16_t wave_start; // wave start LED index
int16_t wave_end; // wave end LED index
float center;
bool goingleft;
float speed_factor;
bool alive = true;
CRGBW basecolor;
public:
void init(uint32_t segment_length, CRGBW color) {
void init(uint32_t segment_length, CRGB color) {
ttl = hw_random16(500, 1501);
basecolor = color;
basealpha = hw_random8(60, 100) * AW_SCALE / 100; // 0-99% note: if using 100% there is risk of integer overflow
basealpha = hw_random8(60, 101) / (float)100;
age = 0;
width = hw_random16(segment_length / 20, segment_length / W_WIDTH_FACTOR) + 1;
center = (((uint32_t)hw_random8(101) << AW_SHIFT) / 100) * segment_length; // 0-100%
goingleft = hw_random8() & 0x01; // 50/50 chance
speed_factor = (((uint32_t)hw_random8(10, 31) * W_MAX_SPEED) << AW_SHIFT) / (100 * 255);
width = hw_random16(segment_length / 20, segment_length / W_WIDTH_FACTOR); //half of width to make math easier
if (!width) width = 1;
center = hw_random8(101) / (float)100 * segment_length;
goingleft = hw_random8(0, 2) == 0;
speed_factor = (hw_random8(10, 31) / (float)100 * W_MAX_SPEED / 255);
alive = true;
}
void updateCachedValues() {
uint32_t half_ttl = ttl >> 1;
if (age < half_ttl) {
ageFactor_cached = ((uint32_t)age << AW_SHIFT) / half_ttl;
} else {
ageFactor_cached = ((uint32_t)(ttl - age) << AW_SHIFT) / half_ttl;
}
if (ageFactor_cached >= AW_SCALE) ageFactor_cached = AW_SCALE - 1; // prevent overflow
CRGB getColorForLED(int ledIndex) {
if(ledIndex < center - width || ledIndex > center + width) return 0; //Position out of range of this wave
uint32_t center_led = center >> AW_SHIFT;
wave_start = (int16_t)center_led - (int16_t)width;
wave_end = (int16_t)center_led + (int16_t)width;
}
CRGB rgb;
CRGBW getColorForLED(int ledIndex) {
// linear brightness falloff from center to edge of wave
if (ledIndex < wave_start || ledIndex > wave_end) return 0;
int32_t ledIndex_scaled = (int32_t)ledIndex << AW_SHIFT;
int32_t offset = ledIndex_scaled - center;
//Offset of this led from center of wave
//The further away from the center, the dimmer the LED
float offset = ledIndex - center;
if (offset < 0) offset = -offset;
uint32_t offsetFactor = offset / width; // scaled by AW_SCALE
if (offsetFactor > AW_SCALE) return 0; // outside of wave
uint32_t brightness_factor = (AW_SCALE - offsetFactor);
brightness_factor = (brightness_factor * ageFactor_cached) >> AW_SHIFT;
brightness_factor = (brightness_factor * basealpha) >> AW_SHIFT;
float offsetFactor = offset / width;
CRGBW rgb;
rgb.r = (basecolor.r * brightness_factor) >> AW_SHIFT;
rgb.g = (basecolor.g * brightness_factor) >> AW_SHIFT;
rgb.b = (basecolor.b * brightness_factor) >> AW_SHIFT;
rgb.w = (basecolor.w * brightness_factor) >> AW_SHIFT;
//The age of the wave determines it brightness.
//At half its maximum age it will be the brightest.
float ageFactor = 0.1;
if((float)age / ttl < 0.5) {
ageFactor = (float)age / (ttl / 2);
} else {
ageFactor = (float)(ttl - age) / ((float)ttl * 0.5);
}
//Calculate color based on above factors and basealpha value
float factor = (1 - offsetFactor) * ageFactor * basealpha;
rgb.r = basecolor.r * factor;
rgb.g = basecolor.g * factor;
rgb.b = basecolor.b * factor;
return rgb;
};
//Change position and age of wave
//Determine if its still "alive"
//Determine if its sill "alive"
void update(uint32_t segment_length, uint32_t speed) {
int32_t step = speed_factor * speed;
center += goingleft ? -step : step;
if(goingleft) {
center -= speed_factor * speed;
} else {
center += speed_factor * speed;
}
age++;
if (age > ttl) {
if(age > ttl) {
alive = false;
} else {
uint32_t width_scaled = (uint32_t)width << AW_SHIFT;
uint32_t segment_length_scaled = segment_length << AW_SHIFT;
if (goingleft) {
if (center < - (int32_t)width_scaled) {
alive = false;
}
} else {
if (center > (int32_t)segment_length_scaled + (int32_t)width_scaled) {
alive = false;
}
}
if(goingleft) {
if(center + width < 0) {
alive = false;
}
} else {
if(center - width > segment_length) {
alive = false;
}
}
}
};
bool stillAlive() { return alive; }
bool stillAlive() {
return alive;
};
};
uint16_t mode_aurora(void) {
AuroraWave* waves;
SEGENV.aux1 = map(SEGMENT.intensity, 0, 255, 2, W_MAX_COUNT); // aux1 = Wavecount
if (!SEGENV.allocateData(sizeof(AuroraWave) * SEGENV.aux1)) {
return mode_static();
if(!SEGENV.allocateData(sizeof(AuroraWave) * SEGENV.aux1)) { // 20 on ESP32, 9 on ESP8266
return mode_static(); //allocation failed
}
waves = reinterpret_cast<AuroraWave*>(SEGENV.data);
// note: on first call, SEGENV.data is zero -> all waves are dead and will be initialized
for (int i = 0; i < SEGENV.aux1; i++) {
waves[i].update(SEGLEN, SEGMENT.speed);
if (!(waves[i].stillAlive())) {
waves[i].init(SEGLEN, SEGMENT.color_from_palette(hw_random8(), false, false, hw_random8(0, 3)));
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))));
}
waves[i].updateCachedValues();
}
uint8_t backlight = 0; // note: original code used 1, with inverse gamma applied background would never be black
for (int i = 0; i < SEGENV.aux1; i++) {
//Update values of wave
waves[i].update(SEGLEN, SEGMENT.speed);
if(!(waves[i].stillAlive())) {
//If a wave dies, reinitialize it starts over.
waves[i].init(SEGLEN, CRGB(SEGMENT.color_from_palette(hw_random8(), false, false, hw_random8(0, 3))));
}
}
uint8_t backlight = 1; //dimmer backlight if less active colors
if (SEGCOLOR(0)) backlight++;
if (SEGCOLOR(1)) backlight++;
if (SEGCOLOR(2)) backlight++;
backlight = gamma8inv(backlight); // preserve backlight when using gamma correction
//Loop through LEDs to determine color
for (unsigned i = 0; i < SEGLEN; i++) {
CRGBW mixedRgb = CRGBW(backlight, backlight, backlight);
CRGB mixedRgb = CRGB(backlight, backlight, backlight);
for (int j = 0; j < SEGENV.aux1; j++) {
CRGBW rgb = waves[j].getColorForLED(i);
mixedRgb = color_add(mixedRgb, rgb); // sum all waves influencing this pixel
//For each LED we must check each wave if it is "active" at this position.
//If there are multiple waves active on a LED we multiply their values.
for (int j = 0; j < SEGENV.aux1; j++) {
CRGB rgb = waves[j].getColorForLED(i);
if(rgb != CRGB(0)) {
mixedRgb += rgb;
}
}
SEGMENT.setPixelColor(i, mixedRgb);
SEGMENT.setPixelColor(i, mixedRgb[0], mixedRgb[1], mixedRgb[2]);
}
return FRAMETIME;
}
static const char _data_FX_MODE_AURORA[] PROGMEM = "Aurora@!,!;1,2,3;!;;sx=24,pal=50";
// WLED-SR effects

View File

@@ -17,7 +17,6 @@
// note: matrix may be comprised of multiple panels each with different orientation
// but ledmap takes care of that. ledmap is constructed upon initialization
// so matrix should disable regular ledmap processing
// WARNING: effect drawing has to be suspended (strip.suspend()) or must be called from loop() context
void WS2812FX::setUpMatrix() {
#ifndef WLED_DISABLE_2D
// isMatrix is set in cfg.cpp or set.cpp
@@ -46,12 +45,12 @@ void WS2812FX::setUpMatrix() {
return;
}
suspend();
waitForIt();
customMappingSize = 0; // prevent use of mapping if anything goes wrong
d_free(customMappingTable);
// Segment::maxWidth and Segment::maxHeight are set according to panel layout
// and the product will include at least all leds in matrix
// if actual LEDs are more, getLengthTotal() will return correct number of LEDs
customMappingTable = static_cast<uint16_t*>(d_malloc(sizeof(uint16_t)*getLengthTotal())); // prefer to not use SPI RAM
if (customMappingTable) {
@@ -114,6 +113,7 @@ void WS2812FX::setUpMatrix() {
// delete gap array as we no longer need it
p_free(gapTable);
resume();
#ifdef WLED_DEBUG
DEBUG_PRINT(F("Matrix ledmap:"));

View File

@@ -1681,17 +1681,12 @@ void WS2812FX::setTransitionMode(bool t) {
resume();
}
// wait until frame is over (service() has finished or time for 2 frames have passed; yield() crashes on 8266)
// the latter may, in rare circumstances, lead to incorrectly assuming strip is done servicing but will not block
// other processing "indefinitely"
// rare circumstances are: setting FPS to high number (i.e. 120) and have very slow effect that will need more
// time than 2 * _frametime (1000/FPS) to draw content
// wait until frame is over (service() has finished or time for 1 frame has passed; yield() crashes on 8266)
void WS2812FX::waitForIt() {
unsigned long waitStart = millis();
unsigned long maxWait = 2*getFrameTime() + 100; // TODO: this needs a proper fix for timeout! see #4779
while (isServicing() && (millis() - waitStart < maxWait)) delay(1); // safe even when millis() rolls over
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()-waitStart >= maxWait) DEBUG_PRINTLN(F("Waited for strip to finish servicing."));
if (millis() >= maxWait) DEBUG_PRINTLN(F("Waited for strip to finish servicing."));
#endif
};
@@ -1815,11 +1810,7 @@ Segment& WS2812FX::getSegment(unsigned id) {
return _segments[id >= _segments.size() ? getMainSegmentId() : id]; // vectors
}
// WARNING: resetSegments(), makeAutoSegments() and fixInvalidSegments() must not be called while
// strip is being serviced (strip.service()), you must call suspend prior if changing segments outside
// loop() context
void WS2812FX::resetSegments() {
if (isServicing()) return;
_segments.clear(); // destructs all Segment as part of clearing
_segments.emplace_back(0, isMatrix ? Segment::maxWidth : _length, 0, isMatrix ? Segment::maxHeight : 1);
_segments.shrink_to_fit(); // just in case ...
@@ -1827,7 +1818,6 @@ void WS2812FX::resetSegments() {
}
void WS2812FX::makeAutoSegments(bool forceReset) {
if (isServicing()) return;
if (autoSegments) { //make one segment per bus
unsigned segStarts[MAX_NUM_SEGMENTS] = {0};
unsigned segStops [MAX_NUM_SEGMENTS] = {0};
@@ -1899,7 +1889,6 @@ void WS2812FX::makeAutoSegments(bool forceReset) {
}
void WS2812FX::fixInvalidSegments() {
if (isServicing()) return;
//make sure no segment is longer than total (sanity check)
for (size_t i = getSegmentsNum()-1; i > 0; i--) {
if (isMatrix) {
@@ -1962,7 +1951,6 @@ void WS2812FX::printSize() {
// load custom mapping table from JSON file (called from finalizeInit() or deserializeState())
// if this is a matrix set-up and default ledmap.json file does not exist, create mapping table using setUpMatrix() from panel information
// WARNING: effect drawing has to be suspended (strip.suspend()) or must be called from loop() context
bool WS2812FX::deserializeMap(unsigned n) {
char fileName[32];
strcpy_P(fileName, PSTR("/ledmap"));
@@ -1992,13 +1980,15 @@ bool WS2812FX::deserializeMap(unsigned n) {
} else
DEBUG_PRINTF_P(PSTR("Reading LED map from %s\n"), fileName);
suspend();
waitForIt();
JsonObject root = pDoc->as<JsonObject>();
// if we are loading default ledmap (at boot) set matrix width and height from the ledmap (compatible with WLED MM ledmaps)
if (n == 0 && (!root[F("width")].isNull() || !root[F("height")].isNull())) {
Segment::maxWidth = min(max(root[F("width")].as<int>(), 1), 255);
Segment::maxHeight = min(max(root[F("height")].as<int>(), 1), 255);
isMatrix = true;
DEBUG_PRINTF_P(PSTR("LED map width=%d, height=%d\n"), Segment::maxWidth, Segment::maxHeight);
}
d_free(customMappingTable);
@@ -2022,9 +2012,9 @@ bool WS2812FX::deserializeMap(unsigned n) {
} while (i < 32);
if (!foundDigit) break;
int index = atoi(number);
if (index < 0 || index > 65535) index = 0xFFFF; // prevent integer wrap around
if (index < 0 || index > 16384) index = 0xFFFF;
customMappingTable[customMappingSize++] = index;
if (customMappingSize >= getLengthTotal()) break;
if (customMappingSize > getLengthTotal()) break;
} else break; // there was nothing to read, stop
}
currentLedmap = n;
@@ -2034,7 +2024,7 @@ bool WS2812FX::deserializeMap(unsigned n) {
DEBUG_PRINT(F("Loaded ledmap:"));
for (unsigned i=0; i<customMappingSize; i++) {
if (!(i%Segment::maxWidth)) DEBUG_PRINTLN();
DEBUG_PRINTF_P(PSTR("%4d,"), customMappingTable[i] < 0xFFFFU ? customMappingTable[i] : -1);
DEBUG_PRINTF_P(PSTR("%4d,"), customMappingTable[i]);
}
DEBUG_PRINTLN();
#endif
@@ -2050,6 +2040,8 @@ bool WS2812FX::deserializeMap(unsigned n) {
DEBUG_PRINTLN(F("ERROR LED map allocation error."));
}
resume();
releaseJSONBufferLock();
return (customMappingSize > 0);
}

View File

@@ -12,7 +12,7 @@
#if !(defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3))
#define LEDC_MUTEX_LOCK() do {} while (xSemaphoreTake(_ledc_sys_lock, portMAX_DELAY) != pdPASS)
#define LEDC_MUTEX_UNLOCK() xSemaphoreGive(_ledc_sys_lock)
extern xSemaphoreHandle _ledc_sys_lock;
extern SemaphoreHandle_t _ledc_sys_lock;
#else
#define LEDC_MUTEX_LOCK()
#define LEDC_MUTEX_UNLOCK()
@@ -467,8 +467,7 @@ BusPwm::BusPwm(const BusConfig &bc)
pinMode(_pins[i], OUTPUT);
#else
unsigned channel = _ledcStart + i;
ledcSetup(channel, _frequency, _depth - (dithering*4)); // with dithering _frequency doesn't really matter as resolution is 8 bit
ledcAttachPin(_pins[i], channel);
ledcAttach(_pins[i], _frequency, _depth - (dithering*4));
// LEDC timer reset credit @dedehai
uint8_t group = (channel / 8), timer = ((channel / 2) % 4); // same fromula as in ledcSetup()
ledc_timer_rst((ledc_mode_t)group, (ledc_timer_t)timer); // reset timer so all timers are almost in sync (for phase shift)
@@ -632,7 +631,7 @@ void BusPwm::deallocatePins() {
#ifdef ESP8266
digitalWrite(_pins[i], LOW); //turn off PWM interrupt
#else
if (_ledcStart < WLED_MAX_ANALOG_CHANNELS) ledcDetachPin(_pins[i]);
if (_ledcStart < WLED_MAX_ANALOG_CHANNELS) ledcDetach(_pins[i]);
#endif
}
#ifdef ARDUINO_ARCH_ESP32
@@ -754,7 +753,7 @@ size_t BusNetwork::getPins(uint8_t* pinArray) const {
#ifdef ARDUINO_ARCH_ESP32
void BusNetwork::resolveHostname() {
static unsigned long nextResolve = 0;
if (Network.isConnected() && millis() > nextResolve && _hostname.length() > 0) {
if (WLEDNetwork.isConnected() && millis() > nextResolve && _hostname.length() > 0) {
nextResolve = millis() + 600000; // resolve only every 10 minutes
IPAddress clnt;
if (strlen(cmDNS) > 0) clnt = MDNS.queryHost(_hostname);

View File

@@ -5,6 +5,20 @@
//#define NPB_CONF_4STEP_CADENCE
#include "NeoPixelBus.h"
// --- temporary shim for NeoPixelBus CORE3 / RMT driver_v2 ------------------
#if __has_include(<NeoPixelBus.h>)
#define NeoEsp32RmtNWs2812xMethod NeoEsp32RmtXWs2812xMethod
#define NeoEsp32RmtNSk6812Method NeoEsp32RmtXSk6812Method
#define NeoEsp32RmtN800KbpsMethod NeoEsp32RmtX800KbpsMethod
#define NeoEsp32RmtN400KbpsMethod NeoEsp32RmtX400KbpsMethod
#define NeoEsp32RmtNTm1814Method NeoEsp32RmtXTm1814Method
#define NeoEsp32RmtNTm1829Method NeoEsp32RmtXTm1829Method
#define NeoEsp32RmtNTm1914Method NeoEsp32RmtXTm1914Method
#define NeoEsp32RmtNApa106Method NeoEsp32RmtXApa106Method
#define NeoEsp32RmtNWs2805Method NeoEsp32RmtXWs2805Method
#endif
// ---------------------------------------------------------------------------
//Hardware SPI Pins
#define P_8266_HS_MOSI 13
#define P_8266_HS_CLK 14

File diff suppressed because it is too large Load Diff

View File

@@ -794,7 +794,7 @@ input[type=range]::-moz-range-thumb {
/* buttons */
.btn {
padding: 8px;
margin: 10px 4px;
/*margin: 10px 4px;*/
width: 230px;
font-size: 19px;
color: var(--c-d);

View File

@@ -672,6 +672,7 @@ function parseInfo(i) {
//syncTglRecv = i.str;
maxSeg = i.leds.maxseg;
pmt = i.fs.pmt;
if (pcMode && !i.wifi.ap) gId('edit').classList.remove("hide"); else gId('edit').classList.add("hide");
gId('buttonNodes').style.display = lastinfo.ndc > 0 ? null:"none";
// do we have a matrix set-up
mw = i.leds.matrix ? i.leds.matrix.w : 0;
@@ -1756,7 +1757,6 @@ function requestJson(command=null)
if (json.info) {
let i = json.info;
parseInfo(i);
checkVersionUpgrade(i); // Check for version upgrade
populatePalettes(i);
if (isInfo) populateInfo(i);
if (simplifiedUI) simplifyUI();
@@ -3151,6 +3151,7 @@ function togglePcMode(fromB = false)
if (!fromB && ((wW < 1024 && lastw < 1024) || (wW >= 1024 && lastw >= 1024))) return; // no change in size and called from size()
if (pcMode) openTab(0, true);
gId('buttonPcm').className = (pcMode) ? "active":"";
if (pcMode && !ap) gId('edit').classList.remove("hide"); else gId('edit').classList.add("hide");
gId('bot').style.height = (pcMode && !cfg.comp.pcmbot) ? "0":"auto";
sCol('--bh', gId('bot').clientHeight + "px");
_C.style.width = (pcMode || simplifiedUI)?'100%':'400%';
@@ -3305,195 +3306,6 @@ function simplifyUI() {
gId("btns").style.display = "none";
}
// Version reporting feature
var versionCheckDone = false;
function checkVersionUpgrade(info) {
// Only check once per page load
if (versionCheckDone) return;
versionCheckDone = true;
// Suppress feature if in AP mode (no internet connection available)
if (info.wifi && info.wifi.ap) return;
// Fetch version-info.json using existing /edit endpoint
fetch(getURL('/edit?func=edit&path=/version-info.json'), {
method: 'get'
})
.then(res => {
if (res.status === 404) {
// File doesn't exist - first install, show install prompt
showVersionUpgradePrompt(info, null, info.ver);
return null;
}
if (!res.ok) {
throw new Error('Failed to fetch version-info.json');
}
return res.json();
})
.then(versionInfo => {
if (!versionInfo) return; // 404 case already handled
// Check if user opted out
if (versionInfo.neverAsk) return;
// Check if version has changed
const currentVersion = info.ver;
const storedVersion = versionInfo.version || '';
if (storedVersion && storedVersion !== currentVersion) {
// Version has changed, show upgrade prompt
showVersionUpgradePrompt(info, storedVersion, currentVersion);
} else if (!storedVersion) {
// Empty version in file, show install prompt
showVersionUpgradePrompt(info, null, currentVersion);
}
})
.catch(e => {
console.log('Failed to load version-info.json', e);
// On error, save current version for next time
if (info && info.ver) {
updateVersionInfo(info.ver, false);
}
});
}
function showVersionUpgradePrompt(info, oldVersion, newVersion) {
// Determine if this is an install or upgrade
const isInstall = !oldVersion;
// Create overlay and dialog
const overlay = d.createElement('div');
overlay.id = 'versionUpgradeOverlay';
overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.7);z-index:10000;display:flex;align-items:center;justify-content:center;';
const dialog = d.createElement('div');
dialog.style.cssText = 'background:var(--c-1);border-radius:10px;padding:25px;max-width:500px;margin:20px;box-shadow:0 4px 6px rgba(0,0,0,0.3);';
// Build contextual message based on install vs upgrade
const title = isInstall
? '🎉 Thank you for installing WLED!'
: '🎉 WLED Upgrade Detected!';
const description = isInstall
? `You are now running WLED <strong style="text-wrap: nowrap">${newVersion}</strong>.`
: `Your WLED has been upgraded from <strong style="text-wrap: nowrap">${oldVersion}</strong> to <strong style="text-wrap: nowrap">${newVersion}</strong>.`;
const question = 'Would you like to help the WLED development team by reporting your installation? This helps us understand what hardware and versions are being used.'
dialog.innerHTML = `
<h2 style="margin-top:0;color:var(--c-f);">${title}</h2>
<p style="color:var(--c-f);">${description}</p>
<p style="color:var(--c-f);">${question}</p>
<p style="color:var(--c-f);font-size:0.9em;">
<a href="https://kno.wled.ge/about/privacy-policy/" target="_blank" style="color:var(--c-6);">Learn more about what data is collected and why</a>
</p>
<div style="margin-top:20px;">
<button id="versionReportYes" class="btn">Yes</button>
<button id="versionReportNo" class="btn">Not Now</button>
<button id="versionReportNever" class="btn">Never Ask</button>
</div>
`;
overlay.appendChild(dialog);
d.body.appendChild(overlay);
// Add event listeners
gId('versionReportYes').addEventListener('click', () => {
reportUpgradeEvent(info, oldVersion, newVersion);
d.body.removeChild(overlay);
});
gId('versionReportNo').addEventListener('click', () => {
// Don't update version, will ask again on next load
d.body.removeChild(overlay);
});
gId('versionReportNever').addEventListener('click', () => {
updateVersionInfo(newVersion, true);
d.body.removeChild(overlay);
showToast('You will not be asked again.');
});
}
function reportUpgradeEvent(info, oldVersion, newVersion) {
showToast('Reporting upgrade...');
// Fetch fresh data from /json/info endpoint as requested
fetch(getURL('/json/info'), {
method: 'get'
})
.then(res => res.json())
.then(infoData => {
// Map to UpgradeEventRequest structure per OpenAPI spec
// Required fields: deviceId, version, previousVersion, releaseName, chip, ledCount, isMatrix, bootloaderSHA256
const upgradeData = {
deviceId: infoData.deviceId, // Use anonymous unique device ID
version: infoData.ver || '', // Current version string
previousVersion: oldVersion || '', // Previous version from version-info.json
releaseName: infoData.release || '', // Release name (e.g., "WLED 0.15.0")
chip: infoData.arch || '', // Chip architecture (esp32, esp8266, etc)
ledCount: infoData.leds ? infoData.leds.count : 0, // Number of LEDs
isMatrix: !!(infoData.leds && infoData.leds.matrix), // Whether it's a 2D matrix setup
bootloaderSHA256: infoData.bootloaderSHA256 || '', // Bootloader SHA256 hash
brand: infoData.brand, // Device brand (always present)
product: infoData.product, // Product name (always present)
flashSize: infoData.flash // Flash size (always present)
};
// Add optional fields if available
if (infoData.psram !== undefined) upgradeData.psramSize = infoData.psram;
// Note: partitionSizes not currently available in /json/info endpoint
// Make AJAX call to postUpgradeEvent API
return fetch('https://usage.wled.me/api/usage/upgrade', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(upgradeData)
});
})
.then(res => {
if (res.ok) {
showToast('Thank you for reporting!');
updateVersionInfo(newVersion, false);
} else {
showToast('Report failed. Please try again later.', true);
// Do NOT update version info on failure - user will be prompted again
}
})
.catch(e => {
console.log('Failed to report upgrade', e);
showToast('Report failed. Please try again later.', true);
// Do NOT update version info on error - user will be prompted again
});
}
function updateVersionInfo(version, neverAsk) {
const versionInfo = {
version: version,
neverAsk: neverAsk
};
// Create a Blob with JSON content and use /upload endpoint
const blob = new Blob([JSON.stringify(versionInfo)], { type: 'application/json' });
const formData = new FormData();
formData.append('data', blob, 'version-info.json');
fetch(getURL('/upload'), {
method: 'POST',
body: formData
})
.then(res => res.text())
.then(data => {
console.log('Version info updated', data);
})
.catch(e => {
console.log('Failed to update version-info.json', e);
});
}
size();
_C.style.setProperty('--n', N);

View File

@@ -17,65 +17,26 @@
}
window.open(getURL("/update?revert"),"_self");
}
function GetV() {
// Fetch device info via JSON API instead of compiling it in
fetch('/json/info')
.then(response => response.json())
.then(data => {
document.querySelector('.installed-version').textContent = `${data.brand} ${data.ver} (${data.vid})`;
document.querySelector('.release-name').textContent = data.release;
// TODO - assemble update URL
// TODO - can this be done at build time?
if (data.arch == "esp8266") {
toggle('rev');
}
const isESP32 = data.arch && (data.arch.toLowerCase() === 'esp32' || data.arch.toLowerCase() === 'esp32-s2');
if (isESP32) {
gId('bootloader-section').style.display = 'block';
if (data.bootloaderSHA256) {
gId('bootloader-hash').innerText = 'Current bootloader SHA256: ' + data.bootloaderSHA256;
}
}
})
.catch(error => {
console.log('Could not fetch device info:', error);
// Fallback to compiled-in value if API call fails
document.querySelector('.installed-version').textContent = 'Unknown';
document.querySelector('.release-name').textContent = 'Unknown';
});
}
function GetV() {/*injected values here*/}
</script>
<style>
@import url("style.css");
</style>
</head>
<body onload="GetV();">
<body onload="GetV()">
<h2>WLED Software Update</h2>
<form method='POST' action='./update' id='upd' enctype='multipart/form-data' onsubmit="toggle('upd')">
Installed version: <span class="sip installed-version">Loading...</span><br>
Release: <span class="sip release-name">Loading...</span><br>
Installed version: <span class="sip">WLED ##VERSION##</span><br>
Download the latest binary: <a href="https://github.com/wled-dev/WLED/releases" target="_blank"
style="vertical-align: text-bottom; display: inline-flex;">
<img src="https://img.shields.io/github/release/wled-dev/WLED.svg?style=flat-square"></a><br>
<input type="hidden" name="skipValidation" value="" id="sV">
<input type='file' name='update' required><br> <!--should have accept='.bin', but it prevents file upload from android app-->
<input type='checkbox' onchange="sV.value=checked?1:''" id="skipValidation">
<label for='skipValidation'>Ignore firmware validation</label><br>
<button type="submit">Update!</button><br>
<hr class="sml">
<button id="rev" type="button" onclick="cR()">Revert update</button><br>
<button type="button" onclick="B()">Back</button>
</form>
<div id="bootloader-section" style="display:none;">
<hr class="sml">
<h2>ESP32 Bootloader Update</h2>
<div id="bootloader-hash" class="sip" style="margin-bottom:8px;"></div>
<form method='POST' action='./updatebootloader' id='bootupd' enctype='multipart/form-data' onsubmit="toggle('bootupd')">
<b>Warning:</b> Only upload verified ESP32 bootloader files!<br>
<input type='file' name='update' required><br>
<button type="submit">Update Bootloader</button>
</form>
</div>
<div id="Noupd" class="hide"><b>Updating...</b><br>Please do not close or refresh the page :)</div>
</body>
</html>

View File

@@ -1,6 +1,7 @@
#include "wled.h"
#ifdef WLED_ENABLE_DMX_INPUT
#pragma message "DMX physical input driver enabled"
#ifdef ESP8266
#error DMX input is only supported on ESP32
@@ -9,8 +10,8 @@
#include "dmx_input.h"
#include <rdm/responder.h>
void rdmPersonalityChangedCb(dmx_port_t dmxPort, const rdm_header_t *header,
void *context)
void rdmPersonalityChangedCb(dmx_port_t dmxPort, rdm_header_t *request_header,
rdm_header_t *response_header, void *context)
{
DMXInput *dmx = static_cast<DMXInput *>(context);
@@ -19,7 +20,7 @@ void rdmPersonalityChangedCb(dmx_port_t dmxPort, const rdm_header_t *header,
return;
}
if (header->cc == RDM_CC_SET_COMMAND_RESPONSE) {
if (response_header->cc == RDM_CC_SET_COMMAND_RESPONSE) {
const uint8_t personality = dmx_get_current_personality(dmx->inputPortNum);
DMXMode = std::min(DMX_MODE_PRESET, std::max(DMX_MODE_SINGLE_RGB, int(personality)));
configNeedsWrite = true;
@@ -27,8 +28,8 @@ void rdmPersonalityChangedCb(dmx_port_t dmxPort, const rdm_header_t *header,
}
}
void rdmAddressChangedCb(dmx_port_t dmxPort, const rdm_header_t *header,
void *context)
void rdmAddressChangedCb(dmx_port_t dmxPort, rdm_header_t *request_header,
rdm_header_t *response_header, void *context)
{
DMXInput *dmx = static_cast<DMXInput *>(context);
@@ -37,7 +38,7 @@ void rdmAddressChangedCb(dmx_port_t dmxPort, const rdm_header_t *header,
return;
}
if (header->cc == RDM_CC_SET_COMMAND_RESPONSE) {
if (response_header->cc == RDM_CC_SET_COMMAND_RESPONSE) {
const uint16_t addr = dmx_get_start_address(dmx->inputPortNum);
DMXAddress = std::min(512, int(addr));
configNeedsWrite = true;
@@ -47,46 +48,53 @@ void rdmAddressChangedCb(dmx_port_t dmxPort, const rdm_header_t *header,
static dmx_config_t createConfig()
{
dmx_config_t config;
config.pd_size = 255;
config.dmx_start_address = DMXAddress;
dmx_config_t config = DMX_CONFIG_DEFAULT;
config.model_id = 0;
config.product_category = RDM_PRODUCT_CATEGORY_FIXTURE;
config.software_version_id = VERSION;
strcpy(config.device_label, "WLED_MM");
const std::string dmxWledVersionString = "WLED_V" + std::to_string(VERSION);
strncpy(config.software_version_label, dmxWledVersionString.c_str(), 32);
config.software_version_label[32] = '\0'; // zero termination in case versionString string was longer than 32 chars
config.personalities[0].description = "SINGLE_RGB";
config.personalities[0].footprint = 3;
config.personalities[1].description = "SINGLE_DRGB";
config.personalities[1].footprint = 4;
config.personalities[2].description = "EFFECT";
config.personalities[2].footprint = 15;
config.personalities[3].description = "MULTIPLE_RGB";
config.personalities[3].footprint = std::min(512, int(strip.getLengthTotal()) * 3);
config.personalities[4].description = "MULTIPLE_DRGB";
config.personalities[4].footprint = std::min(512, int(strip.getLengthTotal()) * 3 + 1);
config.personalities[5].description = "MULTIPLE_RGBW";
config.personalities[5].footprint = std::min(512, int(strip.getLengthTotal()) * 4);
config.personalities[6].description = "EFFECT_W";
config.personalities[6].footprint = 18;
config.personalities[7].description = "EFFECT_SEGMENT";
config.personalities[7].footprint = std::min(512, strip.getSegmentsNum() * 15);
config.personalities[8].description = "EFFECT_SEGMENT_W";
config.personalities[8].footprint = std::min(512, strip.getSegmentsNum() * 18);
config.personalities[9].description = "PRESET";
config.personalities[9].footprint = 1;
config.personality_count = 10;
// rdm personalities are numbered from 1, thus we can just set the DMXMode directly.
config.current_personality = DMXMode;
const std::string versionString = "WLED_V" + std::to_string(VERSION);
config.software_version_label = versionString.c_str();
return config;
}
static dmx_personality_t personalities[10];
static void createPersonalities()
{
// Initialize personalities array
strncpy(personalities[0].description, "SINGLE_RGB", 32);
personalities[0].footprint = 3;
strncpy(personalities[1].description, "SINGLE_DRGB", 32);
personalities[1].footprint = 4;
strncpy(personalities[2].description, "EFFECT", 32);
personalities[2].footprint = 15;
strncpy(personalities[3].description, "MULTIPLE_RGB", 32);
personalities[3].footprint = std::min(512, int(strip.getLengthTotal()) * 3);
strncpy(personalities[4].description, "MULTIPLE_DRGB", 32);
personalities[4].footprint = std::min(512, int(strip.getLengthTotal()) * 3 + 1);
strncpy(personalities[5].description, "MULTIPLE_RGBW", 32);
personalities[5].footprint = std::min(512, int(strip.getLengthTotal()) * 4);
strncpy(personalities[6].description, "EFFECT_W", 32);
personalities[6].footprint = 18;
strncpy(personalities[7].description, "EFFECT_SEGMENT", 32);
personalities[7].footprint = std::min(512, strip.getSegmentsNum() * 15);
strncpy(personalities[8].description, "EFFECT_SEGMENT_W", 32);
personalities[8].footprint = std::min(512, strip.getSegmentsNum() * 18);
strncpy(personalities[9].description, "PRESET", 32);
personalities[9].footprint = 1;
}
void dmxReceiverTask(void *context)
{
DMXInput *instance = static_cast<DMXInput *>(context);
@@ -103,10 +111,11 @@ void dmxReceiverTask(void *context)
bool DMXInput::installDriver()
{
const auto config = createConfig();
createPersonalities();
DEBUG_PRINTF("DMX port: %u\n", inputPortNum);
if (!dmx_driver_install(inputPortNum, &config, DMX_INTR_FLAGS_DEFAULT)) {
if (!dmx_driver_install(inputPortNum, &config, personalities, 10)) {
DEBUG_PRINTF("Error: Failed to install dmx driver\n");
return false;
}
@@ -116,8 +125,14 @@ bool DMXInput::installDriver()
DEBUG_PRINTF("DMX enable pin is: %u\n", enPin);
dmx_set_pin(inputPortNum, txPin, rxPin, enPin);
// Set initial DMX start address and personality
dmx_set_start_address(inputPortNum, DMXAddress);
dmx_set_current_personality(inputPortNum, DMXMode);
// Register RDM callbacks for start address and personality changes
rdm_register_dmx_start_address(inputPortNum, rdmAddressChangedCb, this);
rdm_register_dmx_personality(inputPortNum, rdmPersonalityChangedCb, this);
rdm_register_dmx_personality(inputPortNum, 10, rdmPersonalityChangedCb, this);
initialized = true;
return true;
}
@@ -151,9 +166,9 @@ void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPo
const bool pinsAllocated = PinManager::allocateMultiplePins(pins, 3, PinOwner::DMX_INPUT);
if (!pinsAllocated) {
DEBUG_PRINTF("DMXInput: Error: Failed to allocate pins for DMX_INPUT. Pins already in use:\n");
DEBUG_PRINTF("rx in use by: %s\n", PinManager::getPinOwner(rxPin));
DEBUG_PRINTF("tx in use by: %s\n", PinManager::getPinOwner(txPin));
DEBUG_PRINTF("en in use by: %s\n", PinManager::getPinOwner(enPin));
DEBUG_PRINTF("rx in use by: %hhd\n", PinManager::getPinOwner(rxPin));
DEBUG_PRINTF("tx in use by: %hhd\n", PinManager::getPinOwner(txPin));
DEBUG_PRINTF("en in use by: %hhd\n", PinManager::getPinOwner(enPin));
return;
}
@@ -247,12 +262,11 @@ void DMXInput::enable()
bool DMXInput::isIdentifyOn() const
{
uint8_t identify = 0;
bool identify = false;
const bool gotIdentify = rdm_get_identify_device(inputPortNum, &identify);
// gotIdentify should never be false because it is a default parameter in rdm
// but just in case we check for it anyway
return bool(identify) && gotIdentify;
return identify && gotIdentify;
}
void DMXInput::checkAndUpdateConfig()

View File

@@ -42,12 +42,12 @@ private:
void updateInternal();
// is invoked whenver the dmx start address is changed via rdm
friend void rdmAddressChangedCb(dmx_port_t dmxPort, const rdm_header_t *header,
void *context);
friend void rdmAddressChangedCb(dmx_port_t dmxPort, rdm_header_t *request_header,
rdm_header_t *response_header, void *context);
// is invoked whenever the personality is changed via rdm
friend void rdmPersonalityChangedCb(dmx_port_t dmxPort, const rdm_header_t *header,
void *context);
friend void rdmPersonalityChangedCb(dmx_port_t dmxPort, rdm_header_t *request_header,
rdm_header_t *response_header, void *context);
/// The internal dmx task.
/// This is the main loop of the dmx receiver. It never returns.

View File

@@ -415,14 +415,14 @@ void prepareArtnetPollReply(ArtPollReply *reply) {
reply->reply_opcode = ARTNET_OPCODE_OPPOLLREPLY;
IPAddress localIP = Network.localIP();
IPAddress localIP = WLEDNetwork.localIP();
for (unsigned i = 0; i < 4; i++) {
reply->reply_ip[i] = localIP[i];
}
reply->reply_port = ARTNET_DEFAULT_PORT;
char * numberEnd = (char*) versionString; // strtol promises not to try to edit this.
char * numberEnd = versionString;
reply->reply_version_h = (uint8_t)strtol(numberEnd, &numberEnd, 10);
numberEnd++;
reply->reply_version_l = (uint8_t)strtol(numberEnd, &numberEnd, 10);
@@ -490,7 +490,7 @@ void prepareArtnetPollReply(ArtPollReply *reply) {
// A DMX to / from Art-Net device
reply->reply_style = 0x00;
Network.localMAC(reply->reply_mac);
WLEDNetwork.localMAC(reply->reply_mac);
for (unsigned i = 0; i < 4; i++) {
reply->reply_bind_ip[i] = localIP[i];

View File

@@ -401,8 +401,6 @@ uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxL
int16_t extractModeDefaults(uint8_t mode, const char *segVar);
void checkSettingsPIN(const char *pin);
uint16_t crc16(const unsigned char* data_p, size_t length);
String computeSHA1(const String& input);
String getDeviceId();
uint16_t beatsin88_t(accum88 beats_per_minute_88, uint16_t lowest = 0, uint16_t highest = 65535, uint32_t timebase = 0, uint16_t phase_offset = 0);
uint16_t beatsin16_t(accum88 beats_per_minute, uint16_t lowest = 0, uint16_t highest = 65535, uint32_t timebase = 0, uint16_t phase_offset = 0);
uint8_t beatsin8_t(accum88 beats_per_minute, uint8_t lowest = 0, uint8_t highest = 255, uint32_t timebase = 0, uint8_t phase_offset = 0);
@@ -543,6 +541,7 @@ void handleSerial();
void updateBaudRate(uint32_t rate);
//wled_server.cpp
void createEditHandler(bool enable);
void initServer();
void serveMessage(AsyncWebServerRequest* request, uint16_t code, const String& headl, const String& subl="", byte optionT=255);
void serveJsonError(AsyncWebServerRequest* request, uint16_t code, uint16_t error);

View File

@@ -9,11 +9,11 @@
* Functions to render images from filesystem to segments, used by the "Image" effect
*/
static File file;
static char lastFilename[WLED_MAX_SEGNAME_LEN+2] = "/"; // enough space for "/" + seg.name + '\0'
static GifDecoder<320,320,12,true> decoder; // this creates the basic object; parameter lzwMaxBits is not used; decoder.alloc() always allocated "everything else" = 24Kb
static bool gifDecodeFailed = false;
static unsigned long lastFrameDisplayTime = 0, currentFrameDelay = 0;
File file;
char lastFilename[34] = "/";
GifDecoder<320,320,12,true> decoder;
bool gifDecodeFailed = false;
unsigned long lastFrameDisplayTime = 0, currentFrameDelay = 0;
bool fileSeekCallback(unsigned long position) {
return file.seek(position);
@@ -35,62 +35,29 @@ int fileSizeCallback(void) {
return file.size();
}
bool openGif(const char *filename) { // side-effect: updates "file"
bool openGif(const char *filename) {
file = WLED_FS.open(filename, "r");
DEBUG_PRINTF_P(PSTR("opening GIF file %s\n"), filename);
if (!file) return false;
return true;
}
static Segment* activeSeg;
static uint16_t gifWidth, gifHeight;
static int lastCoordinate; // last coordinate (x+y) that was set, used to reduce redundant pixel writes
static uint16_t perPixelX, perPixelY; // scaling factors when upscaling
Segment* activeSeg;
uint16_t gifWidth, gifHeight;
void screenClearCallback(void) {
activeSeg->fill(0);
}
// this callback runs when the decoder has finished painting all pixels
void updateScreenCallback(void) {
// perfect time for adding blur
if (activeSeg->intensity > 1) {
uint8_t blurAmount = activeSeg->intensity;
if ((blurAmount < 24) && (activeSeg->is2D())) activeSeg->blurRows(activeSeg->intensity); // some blur - fast
else activeSeg->blur(blurAmount); // more blur - slower
}
lastCoordinate = -1; // invalidate last position
}
void updateScreenCallback(void) {}
// note: GifDecoder drawing is done top right to bottom left, line by line
// callbacks to draw a pixel at (x,y) without scaling: used if GIF size matches (virtual)segment size (faster) works for 1D and 2D segments
void drawPixelCallbackNoScale(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t blue) {
activeSeg->setPixelColor(y * gifWidth + x, red, green, blue);
}
void drawPixelCallback1D(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t blue) {
// 1D strip: load pixel-by-pixel left to right, top to bottom (0/0 = top-left in gifs)
int totalImgPix = (int)gifWidth * gifHeight;
int start = ((int)y * gifWidth + (int)x) * activeSeg->vLength() / totalImgPix; // simple nearest-neighbor scaling
if (start == lastCoordinate) return; // skip setting same coordinate again
lastCoordinate = start;
for (int i = 0; i < perPixelX; i++) {
activeSeg->setPixelColor(start + i, red, green, blue);
}
}
void drawPixelCallback2D(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t blue) {
// simple nearest-neighbor scaling
int outY = (int)y * activeSeg->vHeight() / gifHeight;
int outX = (int)x * activeSeg->vWidth() / gifWidth;
// Pack coordinates uniquely: outY into upper 16 bits, outX into lower 16 bits
if (((outY << 16) | outX) == lastCoordinate) return; // skip setting same coordinate again
lastCoordinate = (outY << 16) | outX; // since input is a "scanline" this is sufficient to identify a "unique" coordinate
void drawPixelCallback(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t blue) {
// simple nearest-neighbor scaling
int16_t outY = y * activeSeg->height() / gifHeight;
int16_t outX = x * activeSeg->width() / gifWidth;
// set multiple pixels if upscaling
for (int i = 0; i < perPixelX; i++) {
for (int j = 0; j < perPixelY; j++) {
for (int16_t i = 0; i < (activeSeg->width()+(gifWidth-1)) / gifWidth; i++) {
for (int16_t j = 0; j < (activeSeg->height()+(gifHeight-1)) / gifHeight; j++) {
activeSeg->setPixelColorXY(outX + i, outY + j, red, green, blue);
}
}
@@ -112,88 +79,31 @@ byte renderImageToSegment(Segment &seg) {
if (!seg.name) return IMAGE_ERROR_NO_NAME;
// disable during effect transition, causes flickering, multiple allocations and depending on image, part of old FX remaining
//if (seg.mode != seg.currentMode()) return IMAGE_ERROR_WAITING;
if (activeSeg && activeSeg != &seg) { // only one segment at a time
if (!seg.isActive()) return IMAGE_ERROR_SEG_LIMIT; // sanity check: calling segment must be active
if (gifDecodeFailed || !activeSeg->isActive()) // decoder failed, or last segment became inactive
endImagePlayback(activeSeg); // => allow takeover but clean up first
else
return IMAGE_ERROR_SEG_LIMIT;
}
if (activeSeg && activeSeg != &seg) return IMAGE_ERROR_SEG_LIMIT; // only one segment at a time
activeSeg = &seg;
if (strncmp(lastFilename +1, seg.name, WLED_MAX_SEGNAME_LEN) != 0) { // segment name changed, load new image
strcpy(lastFilename, "/"); // filename always starts with '/'
strncpy(lastFilename +1, seg.name, WLED_MAX_SEGNAME_LEN);
lastFilename[WLED_MAX_SEGNAME_LEN+1] ='\0'; // ensure proper string termination when segment name was truncated
if (strncmp(lastFilename +1, seg.name, 32) != 0) { // segment name changed, load new image
strncpy(lastFilename +1, seg.name, 32);
gifDecodeFailed = false;
size_t fnameLen = strlen(lastFilename);
if ((fnameLen < 4) || strcmp(lastFilename + fnameLen - 4, ".gif") != 0) { // empty segment name, name too short, or name not ending in .gif
if (strcmp(lastFilename + strlen(lastFilename) - 4, ".gif") != 0) {
gifDecodeFailed = true;
DEBUG_PRINTF_P(PSTR("GIF decoder unsupported file: %s\n"), lastFilename);
return IMAGE_ERROR_UNSUPPORTED_FORMAT;
}
if (file) file.close();
if (!openGif(lastFilename)) {
gifDecodeFailed = true;
DEBUG_PRINTF_P(PSTR("GIF file not found: %s\n"), lastFilename);
return IMAGE_ERROR_FILE_MISSING;
}
lastCoordinate = -1;
openGif(lastFilename);
if (!file) { gifDecodeFailed = true; return IMAGE_ERROR_FILE_MISSING; }
decoder.setScreenClearCallback(screenClearCallback);
decoder.setUpdateScreenCallback(updateScreenCallback);
decoder.setDrawPixelCallback(drawPixelCallbackNoScale); // default: use "fast path" callback without scaling
decoder.setDrawPixelCallback(drawPixelCallback);
decoder.setFileSeekCallback(fileSeekCallback);
decoder.setFilePositionCallback(filePositionCallback);
decoder.setFileReadCallback(fileReadCallback);
decoder.setFileReadBlockCallback(fileReadBlockCallback);
decoder.setFileSizeCallback(fileSizeCallback);
#if __cpp_exceptions // use exception handler if we can (some targets don't support exceptions)
try {
#endif
decoder.alloc(); // this function may throw out-of memory and cause a crash
#if __cpp_exceptions
} catch (...) { // if we arrive here, the decoder has thrown an OOM exception
gifDecodeFailed = true;
errorFlag = ERR_NORAM_PX;
DEBUG_PRINTLN(F("\nGIF decoder out of memory. Please try a smaller image file.\n"));
return IMAGE_ERROR_DECODER_ALLOC;
// decoder cleanup (hi @coderabbitai): No additonal cleanup necessary - decoder.alloc() ultimately uses "new AnimatedGIF".
// If new throws, no pointer is assigned, previous decoder state (if any) has already been deleted inside alloc(), so calling decoder.dealloc() here is unnecessary.
}
#endif
decoder.alloc();
DEBUG_PRINTLN(F("Starting decoding"));
int decoderError = decoder.startDecoding();
if(decoderError < 0) {
DEBUG_PRINTF_P(PSTR("GIF Decoding error %d in startDecoding().\n"), decoderError);
errorFlag = ERR_NORAM_PX;
gifDecodeFailed = true;
return IMAGE_ERROR_GIF_DECODE;
}
if(decoder.startDecoding() < 0) { gifDecodeFailed = true; return IMAGE_ERROR_GIF_DECODE; }
DEBUG_PRINTLN(F("Decoding started"));
// after startDecoding, we can get GIF size, update static variables and callbacks
decoder.getSize(&gifWidth, &gifHeight);
if (gifWidth == 0 || gifHeight == 0) { // bad gif size: prevent division by zero
gifDecodeFailed = true;
DEBUG_PRINTF_P(PSTR("Invalid GIF dimensions: %dx%d\n"), gifWidth, gifHeight);
return IMAGE_ERROR_GIF_DECODE;
}
if (activeSeg->is2D()) {
perPixelX = (activeSeg->vWidth() + gifWidth -1) / gifWidth;
perPixelY = (activeSeg->vHeight() + gifHeight-1) / gifHeight;
if (activeSeg->vWidth() != gifWidth || activeSeg->vHeight() != gifHeight) {
decoder.setDrawPixelCallback(drawPixelCallback2D); // use 2D callback with scaling
//DEBUG_PRINTLN(F("scaling image"));
}
} else {
int totalImgPix = (int)gifWidth * gifHeight;
if (totalImgPix - activeSeg->vLength() == 1) totalImgPix--; // handle off-by-one: skip last pixel instead of first (gifs constructed from 1D input pad last pixel if length is odd)
perPixelX = (activeSeg->vLength() + totalImgPix-1) / totalImgPix;
if (totalImgPix != activeSeg->vLength()) {
decoder.setDrawPixelCallback(drawPixelCallback1D); // use 1D callback with scaling
//DEBUG_PRINTLN(F("scaling image"));
}
}
}
if (gifDecodeFailed) return IMAGE_ERROR_PREV;
@@ -207,12 +117,10 @@ byte renderImageToSegment(Segment &seg) {
// TODO consider handling this on FX level with a different frametime, but that would cause slow gifs to speed up during transitions
if (millis() - lastFrameDisplayTime < wait) return IMAGE_ERROR_WAITING;
decoder.getSize(&gifWidth, &gifHeight);
int result = decoder.decodeFrame(false);
if (result < 0) {
DEBUG_PRINTF_P(PSTR("GIF Decoding error %d in decodeFrame().\n"), result);
gifDecodeFailed = true;
return IMAGE_ERROR_FRAME_DECODE;
}
if (result < 0) { gifDecodeFailed = true; return IMAGE_ERROR_FRAME_DECODE; }
currentFrameDelay = decoder.getFrameDelay_ms();
unsigned long tooSlowBy = (millis() - lastFrameDisplayTime) - wait; // if last frame was longer than intended, compensate
@@ -229,8 +137,7 @@ void endImagePlayback(Segment *seg) {
decoder.dealloc();
gifDecodeFailed = false;
activeSeg = nullptr;
strcpy(lastFilename, "/"); // reset filename
gifWidth = gifHeight = 0; // reset dimensions
lastFilename[1] = '\0';
DEBUG_PRINTLN(F("Image playback ended"));
}

View File

@@ -94,7 +94,7 @@ void handleImprovPacket() {
case ImprovRPCType::Request_State: {
unsigned improvState = 0x02; //authorized
if (WLED_WIFI_CONFIGURED) improvState = 0x03; //provisioning
if (Network.isConnected()) improvState = 0x04; //provisioned
if (WLEDNetwork.isConnected()) improvState = 0x04; //provisioned
sendImprovStateResponse(improvState, false);
if (improvState == 0x04) sendImprovIPRPCResult(ImprovRPCType::Request_State);
break;
@@ -178,10 +178,10 @@ void sendImprovRPCResult(ImprovRPCType type, uint8_t n_strings, const char **str
}
void sendImprovIPRPCResult(ImprovRPCType type) {
if (Network.isConnected())
if (WLEDNetwork.isConnected())
{
char urlStr[64];
IPAddress localIP = Network.localIP();
IPAddress localIP = WLEDNetwork.localIP();
unsigned len = sprintf(urlStr, "http://%d.%d.%d.%d", localIP[0], localIP[1], localIP[2], localIP[3]);
if (len > 24) return; //sprintf fail?
const char *str[1] = {urlStr};

View File

@@ -1,6 +1,5 @@
#include "wled.h"
#define JSON_PATH_STATE 1
#define JSON_PATH_INFO 2
#define JSON_PATH_STATE_INFO 3
@@ -52,9 +51,6 @@ namespace {
if (a.custom1 != b.custom1) d |= SEG_DIFFERS_FX;
if (a.custom2 != b.custom2) d |= SEG_DIFFERS_FX;
if (a.custom3 != b.custom3) d |= SEG_DIFFERS_FX;
if (a.check1 != b.check1) d |= SEG_DIFFERS_FX;
if (a.check2 != b.check2) d |= SEG_DIFFERS_FX;
if (a.check3 != b.check3) d |= SEG_DIFFERS_FX;
if (a.startY != b.startY) d |= SEG_DIFFERS_BOUNDS;
if (a.stopY != b.stopY) d |= SEG_DIFFERS_BOUNDS;
@@ -691,7 +687,6 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme
}
}
void serializeInfo(JsonObject root)
{
root[F("ver")] = versionString;
@@ -699,7 +694,6 @@ void serializeInfo(JsonObject root)
root[F("cn")] = F(WLED_CODENAME);
root[F("release")] = releaseString;
root[F("repo")] = repoString;
root[F("deviceId")] = getDeviceId();
JsonObject leds = root.createNestedObject(F("leds"));
leds[F("count")] = strip.getLengthTotal();
@@ -823,9 +817,6 @@ void serializeInfo(JsonObject root)
root[F("resetReason1")] = (int)rtc_get_reset_reason(1);
#endif
root[F("lwip")] = 0; //deprecated
#ifndef WLED_DISABLE_OTA
root[F("bootloaderSHA256")] = getBootloaderSHA256Hex();
#endif
#else
root[F("arch")] = "esp8266";
root[F("core")] = ESP.getCoreVersion();
@@ -885,9 +876,9 @@ void serializeInfo(JsonObject root)
root[F("product")] = F(WLED_PRODUCT_NAME);
root["mac"] = escapedMac;
char s[16] = "";
if (Network.isConnected())
if (WLEDNetwork.isConnected())
{
IPAddress localIP = Network.localIP();
IPAddress localIP = WLEDNetwork.localIP();
sprintf(s, "%d.%d.%d.%d", localIP[0], localIP[1], localIP[2], localIP[3]);
}
root["ip"] = s;

View File

@@ -0,0 +1,50 @@
#include "wled.h"
#ifdef ESP32
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)
#include "mbedtls/sha1.h"
#include "SHA1Builder.h"
// Wrapper functions to map mbedtls SHA1 calls to Arduino SHA1Builder
// This is needed because ESP-IDF 5.x disables SHA1 in mbedtls by default
struct mbedtls_sha1_context_wrapper {
SHA1Builder builder;
};
extern "C" {
void mbedtls_sha1_init(mbedtls_sha1_context *ctx) {
// Allocate wrapper
auto* wrapper = new mbedtls_sha1_context_wrapper();
*(void**)ctx = wrapper;
}
int mbedtls_sha1_starts(mbedtls_sha1_context *ctx) {
auto* wrapper = *(mbedtls_sha1_context_wrapper**)ctx;
wrapper->builder.begin();
return 0;
}
int mbedtls_sha1_update(mbedtls_sha1_context *ctx, const unsigned char *input, size_t ilen) {
auto* wrapper = *(mbedtls_sha1_context_wrapper**)ctx;
wrapper->builder.add((const uint8_t*)input, ilen);
return 0;
}
int mbedtls_sha1_finish(mbedtls_sha1_context *ctx, unsigned char output[20]) {
auto* wrapper = *(mbedtls_sha1_context_wrapper**)ctx;
wrapper->builder.calculate();
wrapper->builder.getBytes(output);
return 0;
}
void mbedtls_sha1_free(mbedtls_sha1_context *ctx) {
auto* wrapper = *(mbedtls_sha1_context_wrapper**)ctx;
delete wrapper;
}
} // extern "C"
#endif
#endif

View File

@@ -222,11 +222,11 @@ bool initEthernet()
#endif
if (!ETH.begin(
(uint8_t) es.eth_address,
(int) es.eth_power,
(eth_phy_type_t) es.eth_type,
(int32_t) es.eth_address,
(int) es.eth_mdc,
(int) es.eth_mdio,
(eth_phy_type_t) es.eth_type,
(int) es.eth_power,
(eth_clock_mode_t) es.eth_clk_mode
)) {
DEBUG_PRINTLN(F("initC: ETH.begin() failed"));
@@ -358,7 +358,7 @@ void WiFiEvent(WiFiEvent_t event)
DEBUG_PRINTF_P(PSTR("WiFi-E: AP Client Connected (%d) @ %lus.\n"), (int)apClients, millis()/1000);
break;
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
DEBUG_PRINT(F("WiFi-E: IP address: ")); DEBUG_PRINTLN(Network.localIP());
DEBUG_PRINT(F("WiFi-E: IP address: ")); DEBUG_PRINTLN(WLEDNetwork.localIP());
break;
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
// followed by IDLE and SCAN_DONE
@@ -396,7 +396,7 @@ void WiFiEvent(WiFiEvent_t event)
if (!apActive) {
WiFi.disconnect(true); // disable WiFi entirely
}
if (multiWiFi[0].staticIP != (uint32_t)0x00000000 && multiWiFi[0].staticGW != (uint32_t)0x00000000) {
if (multiWiFi[0].staticIP != IPAddress(0,0,0,0) && multiWiFi[0].staticGW != IPAddress(0,0,0,0)) {
ETH.config(multiWiFi[0].staticIP, multiWiFi[0].staticGW, multiWiFi[0].staticSN, dnsAddress);
} else {
ETH.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);

View File

@@ -1,741 +0,0 @@
#include "ota_update.h"
#include "wled.h"
#ifdef ESP32
#include <esp_app_format.h>
#include <esp_ota_ops.h>
#include <esp_flash.h>
#include <mbedtls/sha256.h>
#endif
// Platform-specific metadata locations
#ifdef ESP32
constexpr size_t METADATA_OFFSET = 256; // ESP32: metadata appears after Espressif metadata
#define UPDATE_ERROR errorString
const size_t BOOTLOADER_OFFSET = 0x1000;
#elif defined(ESP8266)
constexpr size_t METADATA_OFFSET = 0x1000; // ESP8266: metadata appears at 4KB offset
#define UPDATE_ERROR getErrorString
#endif
constexpr size_t METADATA_SEARCH_RANGE = 512; // bytes
/**
* Check if OTA should be allowed based on release compatibility using custom description
* @param binaryData Pointer to binary file data (not modified)
* @param dataSize Size of binary data in bytes
* @param errorMessage Buffer to store error message if validation fails
* @param errorMessageLen Maximum length of error message buffer
* @return true if OTA should proceed, false if it should be blocked
*/
static bool validateOTA(const uint8_t* binaryData, size_t dataSize, char* errorMessage, size_t errorMessageLen) {
// Clear error message
if (errorMessage && errorMessageLen > 0) {
errorMessage[0] = '\0';
}
// Try to extract WLED structure directly from binary data
wled_metadata_t extractedDesc;
bool hasDesc = findWledMetadata(binaryData, dataSize, &extractedDesc);
if (hasDesc) {
return shouldAllowOTA(extractedDesc, errorMessage, errorMessageLen);
} else {
// No custom description - this could be a legacy binary
if (errorMessage && errorMessageLen > 0) {
strncpy_P(errorMessage, PSTR("This firmware file is missing compatibility metadata."), errorMessageLen - 1);
errorMessage[errorMessageLen - 1] = '\0';
}
return false;
}
}
struct UpdateContext {
// State flags
// FUTURE: the flags could be replaced by a state machine
bool replySent = false;
bool needsRestart = false;
bool updateStarted = false;
bool uploadComplete = false;
bool releaseCheckPassed = false;
String errorMessage;
// Buffer to hold block data across posts, if needed
std::vector<uint8_t> releaseMetadataBuffer;
};
static void endOTA(AsyncWebServerRequest *request) {
UpdateContext* context = reinterpret_cast<UpdateContext*>(request->_tempObject);
request->_tempObject = nullptr;
DEBUG_PRINTF_P(PSTR("EndOTA %x --> %x (%d)\n"), (uintptr_t)request,(uintptr_t) context, context ? context->uploadComplete : 0);
if (context) {
if (context->updateStarted) { // We initialized the update
// We use Update.end() because not all forms of Update() support an abort.
// If the upload is incomplete, Update.end(false) should error out.
if (Update.end(context->uploadComplete)) {
// Update successful!
#ifndef ESP8266
bootloopCheckOTA(); // let the bootloop-checker know there was an OTA update
#endif
doReboot = true;
context->needsRestart = false;
}
}
if (context->needsRestart) {
strip.resume();
UsermodManager::onUpdateBegin(false);
#if WLED_WATCHDOG_TIMEOUT > 0
WLED::instance().enableWatchdog();
#endif
}
delete context;
}
};
static bool beginOTA(AsyncWebServerRequest *request, UpdateContext* context)
{
#ifdef ESP8266
Update.runAsync(true);
#endif
if (Update.isRunning()) {
request->send(503);
setOTAReplied(request);
return false;
}
#if WLED_WATCHDOG_TIMEOUT > 0
WLED::instance().disableWatchdog();
#endif
UsermodManager::onUpdateBegin(true); // notify usermods that update is about to begin (some may require task de-init)
strip.suspend();
backupConfig(); // backup current config in case the update ends badly
strip.resetSegments(); // free as much memory as you can
context->needsRestart = true;
DEBUG_PRINTF_P(PSTR("OTA Update Start, %x --> %x\n"), (uintptr_t)request,(uintptr_t) context);
auto skipValidationParam = request->getParam("skipValidation", true);
if (skipValidationParam && (skipValidationParam->value() == "1")) {
context->releaseCheckPassed = true;
DEBUG_PRINTLN(F("OTA validation skipped by user"));
}
// Begin update with the firmware size from content length
size_t updateSize = request->contentLength() > 0 ? request->contentLength() : ((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000);
if (!Update.begin(updateSize)) {
context->errorMessage = Update.UPDATE_ERROR();
DEBUG_PRINTF_P(PSTR("OTA Failed to begin: %s\n"), context->errorMessage.c_str());
return false;
}
context->updateStarted = true;
return true;
}
// Create an OTA context object on an AsyncWebServerRequest
// Returns true if successful, false on failure.
bool initOTA(AsyncWebServerRequest *request) {
// Allocate update context
UpdateContext* context = new (std::nothrow) UpdateContext {};
if (context) {
request->_tempObject = context;
request->onDisconnect([=]() { endOTA(request); }); // ensures we restart on failure
};
DEBUG_PRINTF_P(PSTR("OTA Update init, %x --> %x\n"), (uintptr_t)request,(uintptr_t) context);
return (context != nullptr);
}
void setOTAReplied(AsyncWebServerRequest *request) {
UpdateContext* context = reinterpret_cast<UpdateContext*>(request->_tempObject);
if (!context) return;
context->replySent = true;
};
// Returns pointer to error message, or nullptr if OTA was successful.
std::pair<bool, String> getOTAResult(AsyncWebServerRequest* request) {
UpdateContext* context = reinterpret_cast<UpdateContext*>(request->_tempObject);
if (!context) return { true, F("OTA context unexpectedly missing") };
if (context->replySent) return { false, {} };
if (context->errorMessage.length()) return { true, context->errorMessage };
if (context->updateStarted) {
// Release the OTA context now.
endOTA(request);
if (Update.hasError()) {
return { true, Update.UPDATE_ERROR() };
} else {
return { true, {} };
}
}
// Should never happen
return { true, F("Internal software failure") };
}
void handleOTAData(AsyncWebServerRequest *request, size_t index, uint8_t *data, size_t len, bool isFinal)
{
UpdateContext* context = reinterpret_cast<UpdateContext*>(request->_tempObject);
if (!context) return;
//DEBUG_PRINTF_P(PSTR("HandleOTAData: %d %d %d\n"), index, len, isFinal);
if (context->replySent || (context->errorMessage.length())) return;
if (index == 0) {
if (!beginOTA(request, context)) return;
}
// Perform validation if we haven't done it yet and we have reached the metadata offset
if (!context->releaseCheckPassed && (index+len) > METADATA_OFFSET) {
// Current chunk contains the metadata offset
size_t availableDataAfterOffset = (index + len) - METADATA_OFFSET;
DEBUG_PRINTF_P(PSTR("OTA metadata check: %d in buffer, %d received, %d available\n"), context->releaseMetadataBuffer.size(), len, availableDataAfterOffset);
if (availableDataAfterOffset >= METADATA_SEARCH_RANGE) {
// We have enough data to validate, one way or another
const uint8_t* search_data = data;
size_t search_len = len;
// If we have saved data, use that instead
if (context->releaseMetadataBuffer.size()) {
// Add this data
context->releaseMetadataBuffer.insert(context->releaseMetadataBuffer.end(), data, data+len);
search_data = context->releaseMetadataBuffer.data();
search_len = context->releaseMetadataBuffer.size();
}
// Do the checking
char errorMessage[128];
bool OTA_ok = validateOTA(search_data, search_len, errorMessage, sizeof(errorMessage));
// Release buffer if there was one
context->releaseMetadataBuffer = decltype(context->releaseMetadataBuffer){};
if (!OTA_ok) {
DEBUG_PRINTF_P(PSTR("OTA declined: %s\n"), errorMessage);
context->errorMessage = errorMessage;
context->errorMessage += F(" Enable 'Ignore firmware validation' to proceed anyway.");
return;
} else {
DEBUG_PRINTLN(F("OTA allowed: Release compatibility check passed"));
context->releaseCheckPassed = true;
}
} else {
// Store the data we just got for next pass
context->releaseMetadataBuffer.insert(context->releaseMetadataBuffer.end(), data, data+len);
}
}
// Check if validation was still pending (shouldn't happen normally)
// This is done before writing the last chunk, so endOTA can abort
if (isFinal && !context->releaseCheckPassed) {
DEBUG_PRINTLN(F("OTA failed: Validation never completed"));
// Don't write the last chunk to the updater: this will trip an error later
context->errorMessage = F("Release check data never arrived?");
return;
}
// Write chunk data to OTA update (only if release check passed or still pending)
if (!Update.hasError()) {
if (Update.write(data, len) != len) {
DEBUG_PRINTF_P(PSTR("OTA write failed on chunk %zu: %s\n"), index, Update.UPDATE_ERROR());
}
}
if(isFinal) {
DEBUG_PRINTLN(F("OTA Update End"));
// Upload complete
context->uploadComplete = true;
}
}
void markOTAvalid() {
#ifndef ESP8266
const esp_partition_t* running = esp_ota_get_running_partition();
esp_ota_img_states_t ota_state;
if (esp_ota_get_state_partition(running, &ota_state) == ESP_OK) {
if (ota_state == ESP_OTA_IMG_PENDING_VERIFY) {
esp_ota_mark_app_valid_cancel_rollback(); // only needs to be called once, it marks the ota_state as ESP_OTA_IMG_VALID
DEBUG_PRINTLN(F("Current firmware validated"));
}
}
#endif
}
#if defined(ARDUINO_ARCH_ESP32) && !defined(WLED_DISABLE_OTA)
// Cache for bootloader SHA256 digest as hex string
static String bootloaderSHA256HexCache = "";
// Calculate and cache the bootloader SHA256 digest as hex string
void calculateBootloaderSHA256() {
if (!bootloaderSHA256HexCache.isEmpty()) return;
// Bootloader is at fixed offset 0x1000 (4KB) and is typically 32KB
const uint32_t bootloaderSize = 0x8000; // 32KB, typical bootloader size
// Calculate SHA256
uint8_t sha256[32];
mbedtls_sha256_context ctx;
mbedtls_sha256_init(&ctx);
mbedtls_sha256_starts(&ctx, 0); // 0 = SHA256 (not SHA224)
const size_t chunkSize = 256;
uint8_t buffer[chunkSize];
for (uint32_t offset = 0; offset < bootloaderSize; offset += chunkSize) {
size_t readSize = min((size_t)(bootloaderSize - offset), chunkSize);
if (esp_flash_read(NULL, buffer, BOOTLOADER_OFFSET + offset, readSize) == ESP_OK) {
mbedtls_sha256_update(&ctx, buffer, readSize);
}
}
mbedtls_sha256_finish(&ctx, sha256);
mbedtls_sha256_free(&ctx);
// Convert to hex string and cache it
char hex[65];
for (int i = 0; i < 32; i++) {
sprintf(hex + (i * 2), "%02x", sha256[i]);
}
hex[64] = '\0';
bootloaderSHA256HexCache = String(hex);
}
// Get bootloader SHA256 as hex string
String getBootloaderSHA256Hex() {
calculateBootloaderSHA256();
return bootloaderSHA256HexCache;
}
// Invalidate cached bootloader SHA256 (call after bootloader update)
void invalidateBootloaderSHA256Cache() {
bootloaderSHA256HexCache = "";
}
// Verify complete buffered bootloader using ESP-IDF validation approach
// This matches the key validation steps from esp_image_verify() in ESP-IDF
// Returns the actual bootloader data pointer and length via the buffer and len parameters
bool verifyBootloaderImage(const uint8_t* &buffer, size_t &len, String* bootloaderErrorMsg) {
size_t availableLen = len;
if (!bootloaderErrorMsg) {
DEBUG_PRINTLN(F("bootloaderErrorMsg is null"));
return false;
}
// ESP32 image header structure (based on esp_image_format.h)
// Offset 0: magic (0xE9)
// Offset 1: segment_count
// Offset 2: spi_mode
// Offset 3: spi_speed (4 bits) + spi_size (4 bits)
// Offset 4-7: entry_addr (uint32_t)
// Offset 8: wp_pin
// Offset 9-11: spi_pin_drv[3]
// Offset 12-13: chip_id (uint16_t, little-endian)
// Offset 14: min_chip_rev
// Offset 15-22: reserved[8]
// Offset 23: hash_appended
const size_t MIN_IMAGE_HEADER_SIZE = 24;
// 1. Validate minimum size for header
if (len < MIN_IMAGE_HEADER_SIZE) {
*bootloaderErrorMsg = "Bootloader too small - invalid header";
return false;
}
// Check if the bootloader starts at offset 0x1000 (common in partition table dumps)
// This happens when someone uploads a complete flash dump instead of just the bootloader
if (len > BOOTLOADER_OFFSET + MIN_IMAGE_HEADER_SIZE &&
buffer[BOOTLOADER_OFFSET] == 0xE9 &&
buffer[0] != 0xE9) {
DEBUG_PRINTF_P(PSTR("Bootloader magic byte detected at offset 0x%04X - adjusting buffer\n"), BOOTLOADER_OFFSET);
// Adjust buffer pointer to start at the actual bootloader
buffer = buffer + BOOTLOADER_OFFSET;
len = len - BOOTLOADER_OFFSET;
// Re-validate size after adjustment
if (len < MIN_IMAGE_HEADER_SIZE) {
*bootloaderErrorMsg = "Bootloader at offset 0x1000 too small - invalid header";
return false;
}
}
// 2. Magic byte check (matches esp_image_verify step 1)
if (buffer[0] != 0xE9) {
*bootloaderErrorMsg = "Invalid bootloader magic byte (expected 0xE9, got 0x" + String(buffer[0], HEX) + ")";
return false;
}
// 3. Segment count validation (matches esp_image_verify step 2)
uint8_t segmentCount = buffer[1];
if (segmentCount == 0 || segmentCount > 16) {
*bootloaderErrorMsg = "Invalid segment count: " + String(segmentCount);
return false;
}
// 4. SPI mode validation (basic sanity check)
uint8_t spiMode = buffer[2];
if (spiMode > 3) { // Valid modes are 0-3 (QIO, QOUT, DIO, DOUT)
*bootloaderErrorMsg = "Invalid SPI mode: " + String(spiMode);
return false;
}
// 5. Chip ID validation (matches esp_image_verify step 3)
uint16_t chipId = buffer[12] | (buffer[13] << 8); // Little-endian
// Known ESP32 chip IDs from ESP-IDF:
// 0x0000 = ESP32
// 0x0002 = ESP32-S2
// 0x0005 = ESP32-C3
// 0x0009 = ESP32-S3
// 0x000C = ESP32-C2
// 0x000D = ESP32-C6
// 0x0010 = ESP32-H2
#if defined(CONFIG_IDF_TARGET_ESP32)
if (chipId != 0x0000) {
*bootloaderErrorMsg = "Chip ID mismatch - expected ESP32 (0x0000), got 0x" + String(chipId, HEX);
return false;
}
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
if (chipId != 0x0002) {
*bootloaderErrorMsg = "Chip ID mismatch - expected ESP32-S2 (0x0002), got 0x" + String(chipId, HEX);
return false;
}
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
if (chipId != 0x0005) {
*bootloaderErrorMsg = "Chip ID mismatch - expected ESP32-C3 (0x0005), got 0x" + String(chipId, HEX);
return false;
}
*bootloaderErrorMsg = "ESP32-C3 update not supported yet";
return false;
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
if (chipId != 0x0009) {
*bootloaderErrorMsg = "Chip ID mismatch - expected ESP32-S3 (0x0009), got 0x" + String(chipId, HEX);
return false;
}
*bootloaderErrorMsg = "ESP32-S3 update not supported yet";
return false;
#elif defined(CONFIG_IDF_TARGET_ESP32C6)
if (chipId != 0x000D) {
*bootloaderErrorMsg = "Chip ID mismatch - expected ESP32-C6 (0x000D), got 0x" + String(chipId, HEX);
return false;
}
*bootloaderErrorMsg = "ESP32-C6 update not supported yet";
return false;
#else
// Generic validation - chip ID should be valid
if (chipId > 0x00FF) {
*bootloaderErrorMsg = "Invalid chip ID: 0x" + String(chipId, HEX);
return false;
}
*bootloaderErrorMsg = "Unknown ESP32 target - bootloader update not supported";
return false;
#endif
// 6. Entry point validation (should be in valid memory range)
uint32_t entryAddr = buffer[4] | (buffer[5] << 8) | (buffer[6] << 16) | (buffer[7] << 24);
// ESP32 bootloader entry points are typically in IRAM range (0x40000000 - 0x40400000)
// or ROM range (0x40000000 and above)
if (entryAddr < 0x40000000 || entryAddr > 0x50000000) {
*bootloaderErrorMsg = "Invalid entry address: 0x" + String(entryAddr, HEX);
return false;
}
// 7. Basic segment structure validation
// Each segment has a header: load_addr (4 bytes) + data_len (4 bytes)
size_t offset = MIN_IMAGE_HEADER_SIZE;
size_t actualBootloaderSize = MIN_IMAGE_HEADER_SIZE;
for (uint8_t i = 0; i < segmentCount && offset + 8 <= len; i++) {
uint32_t segmentSize = buffer[offset + 4] | (buffer[offset + 5] << 8) |
(buffer[offset + 6] << 16) | (buffer[offset + 7] << 24);
// Segment size sanity check
// ESP32 classic bootloader segments can be larger, C3 are smaller
if (segmentSize > 0x20000) { // 128KB max per segment (very generous)
*bootloaderErrorMsg = "Segment " + String(i) + " too large: " + String(segmentSize) + " bytes";
return false;
}
offset += 8 + segmentSize; // Skip segment header and data
}
actualBootloaderSize = offset;
// 8. Check for appended SHA256 hash (byte 23 in header)
// If hash_appended != 0, there's a 32-byte SHA256 hash after the segments
uint8_t hashAppended = buffer[23];
if (hashAppended != 0) {
actualBootloaderSize += 32;
if (actualBootloaderSize > availableLen) {
*bootloaderErrorMsg = "Bootloader missing SHA256 trailer";
return false;
}
DEBUG_PRINTF_P(PSTR("Bootloader has appended SHA256 hash\n"));
}
// 9. The image may also have a 1-byte checksum after segments/hash
// Check if there's at least one more byte available
if (actualBootloaderSize + 1 <= availableLen) {
// There's likely a checksum byte
actualBootloaderSize += 1;
} else if (actualBootloaderSize > availableLen) {
*bootloaderErrorMsg = "Bootloader truncated before checksum";
return false;
}
// 10. Align to 16 bytes (ESP32 requirement for flash writes)
// The bootloader image must be 16-byte aligned
if (actualBootloaderSize % 16 != 0) {
size_t alignedSize = ((actualBootloaderSize + 15) / 16) * 16;
// Make sure we don't exceed available data
if (alignedSize <= len) {
actualBootloaderSize = alignedSize;
}
}
DEBUG_PRINTF_P(PSTR("Bootloader validation: %d segments, actual size %d bytes (buffer size %d bytes, hash_appended=%d)\n"),
segmentCount, actualBootloaderSize, len, hashAppended);
// 11. Verify we have enough data for all segments + hash + checksum
if (actualBootloaderSize > availableLen) {
*bootloaderErrorMsg = "Bootloader truncated - expected at least " + String(actualBootloaderSize) + " bytes, have " + String(availableLen) + " bytes";
return false;
}
if (offset > availableLen) {
*bootloaderErrorMsg = "Bootloader truncated - expected at least " + String(offset) + " bytes, have " + String(len) + " bytes";
return false;
}
// Update len to reflect actual bootloader size (including hash and checksum, with alignment)
// This is critical - we must write the complete image including checksums
len = actualBootloaderSize;
return true;
}
// Bootloader OTA context structure
struct BootloaderUpdateContext {
// State flags
bool replySent = false;
bool uploadComplete = false;
String errorMessage;
// Buffer to hold bootloader data
uint8_t* buffer = nullptr;
size_t bytesBuffered = 0;
const uint32_t bootloaderOffset = 0x1000;
const uint32_t maxBootloaderSize = 0x10000; // 64KB buffer size
};
// Cleanup bootloader OTA context
static void endBootloaderOTA(AsyncWebServerRequest *request) {
BootloaderUpdateContext* context = reinterpret_cast<BootloaderUpdateContext*>(request->_tempObject);
request->_tempObject = nullptr;
DEBUG_PRINTF_P(PSTR("EndBootloaderOTA %x --> %x\n"), (uintptr_t)request, (uintptr_t)context);
if (context) {
if (context->buffer) {
free(context->buffer);
context->buffer = nullptr;
}
// If update failed, restore system state
if (!context->uploadComplete || !context->errorMessage.isEmpty()) {
strip.resume();
#if WLED_WATCHDOG_TIMEOUT > 0
WLED::instance().enableWatchdog();
#endif
}
delete context;
}
}
// Initialize bootloader OTA context
bool initBootloaderOTA(AsyncWebServerRequest *request) {
if (request->_tempObject) {
return true; // Already initialized
}
BootloaderUpdateContext* context = new BootloaderUpdateContext();
if (!context) {
DEBUG_PRINTLN(F("Failed to allocate bootloader OTA context"));
return false;
}
request->_tempObject = context;
request->onDisconnect([=]() { endBootloaderOTA(request); }); // ensures cleanup on disconnect
DEBUG_PRINTLN(F("Bootloader Update Start - initializing buffer"));
#if WLED_WATCHDOG_TIMEOUT > 0
WLED::instance().disableWatchdog();
#endif
lastEditTime = millis(); // make sure PIN does not lock during update
strip.suspend();
strip.resetSegments();
// Check available heap before attempting allocation
size_t freeHeap = getFreeHeapSize();
DEBUG_PRINTF_P(PSTR("Free heap before bootloader buffer allocation: %d bytes (need %d bytes)\n"), freeHeap, context->maxBootloaderSize);
context->buffer = (uint8_t*)malloc(context->maxBootloaderSize);
if (!context->buffer) {
size_t freeHeapNow = getFreeHeapSize();
DEBUG_PRINTF_P(PSTR("Failed to allocate %d byte bootloader buffer! Free heap: %d bytes\n"), context->maxBootloaderSize, freeHeapNow);
context->errorMessage = "Out of memory! Free heap: " + String(freeHeapNow) + " bytes, need: " + String(context->maxBootloaderSize) + " bytes";
strip.resume();
#if WLED_WATCHDOG_TIMEOUT > 0
WLED::instance().enableWatchdog();
#endif
return false;
}
context->bytesBuffered = 0;
return true;
}
// Set bootloader OTA replied flag
void setBootloaderOTAReplied(AsyncWebServerRequest *request) {
BootloaderUpdateContext* context = reinterpret_cast<BootloaderUpdateContext*>(request->_tempObject);
if (context) {
context->replySent = true;
}
}
// Get bootloader OTA result
std::pair<bool, String> getBootloaderOTAResult(AsyncWebServerRequest *request) {
BootloaderUpdateContext* context = reinterpret_cast<BootloaderUpdateContext*>(request->_tempObject);
if (!context) {
return std::make_pair(true, String(F("Internal error: No bootloader OTA context")));
}
bool needsReply = !context->replySent;
String errorMsg = context->errorMessage;
// If upload was successful, return empty string and trigger reboot
if (context->uploadComplete && errorMsg.isEmpty()) {
doReboot = true;
endBootloaderOTA(request);
return std::make_pair(needsReply, String());
}
// If there was an error, return it
if (!errorMsg.isEmpty()) {
endBootloaderOTA(request);
return std::make_pair(needsReply, errorMsg);
}
// Should never happen
return std::make_pair(true, String(F("Internal software failure")));
}
// Handle bootloader OTA data
void handleBootloaderOTAData(AsyncWebServerRequest *request, size_t index, uint8_t *data, size_t len, bool isFinal) {
BootloaderUpdateContext* context = reinterpret_cast<BootloaderUpdateContext*>(request->_tempObject);
if (!context) {
DEBUG_PRINTLN(F("No bootloader OTA context - ignoring data"));
return;
}
if (!context->errorMessage.isEmpty()) {
return;
}
// Buffer the incoming data
if (context->buffer && context->bytesBuffered + len <= context->maxBootloaderSize) {
memcpy(context->buffer + context->bytesBuffered, data, len);
context->bytesBuffered += len;
DEBUG_PRINTF_P(PSTR("Bootloader buffer progress: %d / %d bytes\n"), context->bytesBuffered, context->maxBootloaderSize);
} else if (!context->buffer) {
DEBUG_PRINTLN(F("Bootloader buffer not allocated!"));
context->errorMessage = "Internal error: Bootloader buffer not allocated";
return;
} else {
size_t totalSize = context->bytesBuffered + len;
DEBUG_PRINTLN(F("Bootloader size exceeds maximum!"));
context->errorMessage = "Bootloader file too large: " + String(totalSize) + " bytes (max: " + String(context->maxBootloaderSize) + " bytes)";
return;
}
// Only write to flash when upload is complete
if (isFinal) {
DEBUG_PRINTLN(F("Bootloader Upload Complete - validating and flashing"));
if (context->buffer && context->bytesBuffered > 0) {
// Prepare pointers for verification (may be adjusted if bootloader at offset)
const uint8_t* bootloaderData = context->buffer;
size_t bootloaderSize = context->bytesBuffered;
// Verify the complete bootloader image before flashing
// Note: verifyBootloaderImage may adjust bootloaderData pointer and bootloaderSize
// for validation purposes only
if (!verifyBootloaderImage(bootloaderData, bootloaderSize, &context->errorMessage)) {
DEBUG_PRINTLN(F("Bootloader validation failed!"));
// Error message already set by verifyBootloaderImage
} else {
// Calculate offset to write to flash
// If bootloaderData was adjusted (partition table detected), we need to skip it in flash too
size_t flashOffset = context->bootloaderOffset;
const uint8_t* dataToWrite = context->buffer;
size_t bytesToWrite = context->bytesBuffered;
// If validation adjusted the pointer, it means we have a partition table at the start
// In this case, we should skip writing the partition table and write bootloader at 0x1000
if (bootloaderData != context->buffer) {
// bootloaderData was adjusted - skip partition table in our data
size_t partitionTableSize = bootloaderData - context->buffer;
dataToWrite = bootloaderData;
bytesToWrite = bootloaderSize;
DEBUG_PRINTF_P(PSTR("Skipping %d bytes of partition table data\n"), partitionTableSize);
}
DEBUG_PRINTF_P(PSTR("Bootloader validation passed - writing %d bytes to flash at 0x%04X\n"),
bytesToWrite, flashOffset);
// Calculate erase size (must be multiple of 4KB)
size_t eraseSize = ((bytesToWrite + 0xFFF) / 0x1000) * 0x1000;
if (eraseSize > context->maxBootloaderSize) {
eraseSize = context->maxBootloaderSize;
}
// Erase bootloader region
DEBUG_PRINTF_P(PSTR("Erasing %d bytes at 0x%04X...\n"), eraseSize, flashOffset);
esp_err_t err = esp_flash_erase_region(NULL, flashOffset, eraseSize);
if (err != ESP_OK) {
DEBUG_PRINTF_P(PSTR("Bootloader erase error: %d\n"), err);
context->errorMessage = "Flash erase failed (error code: " + String(err) + ")";
} else {
// Write the validated bootloader data to flash
err = esp_flash_write(NULL, dataToWrite, flashOffset, bytesToWrite);
if (err != ESP_OK) {
DEBUG_PRINTF_P(PSTR("Bootloader flash write error: %d\n"), err);
context->errorMessage = "Flash write failed (error code: " + String(err) + ")";
} else {
DEBUG_PRINTF_P(PSTR("Bootloader Update Success - %d bytes written to 0x%04X\n"),
bytesToWrite, flashOffset);
// Invalidate cached bootloader hash
invalidateBootloaderSHA256Cache();
context->uploadComplete = true;
}
}
}
} else if (context->bytesBuffered == 0) {
context->errorMessage = "No bootloader data received";
}
}
}
#endif

View File

@@ -1,120 +0,0 @@
// WLED OTA update interface
#include <Arduino.h>
#ifdef ESP8266
#include <Updater.h>
#else
#include <Update.h>
#endif
#pragma once
// Platform-specific metadata locations
#ifdef ESP32
#define BUILD_METADATA_SECTION ".rodata_custom_desc"
#elif defined(ESP8266)
#define BUILD_METADATA_SECTION ".ver_number"
#endif
class AsyncWebServerRequest;
/**
* Create an OTA context object on an AsyncWebServerRequest
* @param request Pointer to web request object
* @return true if allocation was successful, false if not
*/
bool initOTA(AsyncWebServerRequest *request);
/**
* Indicate to the OTA subsystem that a reply has already been generated
* @param request Pointer to web request object
*/
void setOTAReplied(AsyncWebServerRequest *request);
/**
* Retrieve the OTA result.
* @param request Pointer to web request object
* @return bool indicating if a reply is necessary; string with error message if the update failed.
*/
std::pair<bool, String> getOTAResult(AsyncWebServerRequest *request);
/**
* Process a block of OTA data. This is a passthrough of an ArUploadHandlerFunction.
* Requires that initOTA be called on the handler object before any work will be done.
* @param request Pointer to web request object
* @param index Offset in to uploaded file
* @param data New data bytes
* @param len Length of new data bytes
* @param isFinal Indicates that this is the last block
* @return bool indicating if a reply is necessary; string with error message if the update failed.
*/
void handleOTAData(AsyncWebServerRequest *request, size_t index, uint8_t *data, size_t len, bool isFinal);
/**
* Mark currently running firmware as valid to prevent auto-rollback on reboot.
* This option can be enabled in some builds/bootloaders, it is an sdkconfig flag.
*/
void markOTAvalid();
#if defined(ARDUINO_ARCH_ESP32) && !defined(WLED_DISABLE_OTA)
/**
* Calculate and cache the bootloader SHA256 digest
* Reads the bootloader from flash at offset 0x1000 and computes SHA256 hash
*/
void calculateBootloaderSHA256();
/**
* Get bootloader SHA256 as hex string
* @return String containing 64-character hex representation of SHA256 hash
*/
String getBootloaderSHA256Hex();
/**
* Invalidate cached bootloader SHA256 (call after bootloader update)
* Forces recalculation on next call to calculateBootloaderSHA256 or getBootloaderSHA256Hex
*/
void invalidateBootloaderSHA256Cache();
/**
* Verify complete buffered bootloader using ESP-IDF validation approach
* This matches the key validation steps from esp_image_verify() in ESP-IDF
* @param buffer Reference to pointer to bootloader binary data (will be adjusted if offset detected)
* @param len Reference to length of bootloader data (will be adjusted to actual size)
* @param bootloaderErrorMsg Pointer to String to store error message (must not be null)
* @return true if validation passed, false otherwise
*/
bool verifyBootloaderImage(const uint8_t* &buffer, size_t &len, String* bootloaderErrorMsg);
/**
* Create a bootloader OTA context object on an AsyncWebServerRequest
* @param request Pointer to web request object
* @return true if allocation was successful, false if not
*/
bool initBootloaderOTA(AsyncWebServerRequest *request);
/**
* Indicate to the bootloader OTA subsystem that a reply has already been generated
* @param request Pointer to web request object
*/
void setBootloaderOTAReplied(AsyncWebServerRequest *request);
/**
* Retrieve the bootloader OTA result.
* @param request Pointer to web request object
* @return bool indicating if a reply is necessary; string with error message if the update failed.
*/
std::pair<bool, String> getBootloaderOTAResult(AsyncWebServerRequest *request);
/**
* Process a block of bootloader OTA data. This is a passthrough of an ArUploadHandlerFunction.
* Requires that initBootloaderOTA be called on the handler object before any work will be done.
* @param request Pointer to web request object
* @param index Offset in to uploaded file
* @param data New data bytes
* @param len Length of new data bytes
* @param isFinal Indicates that this is the last block
*/
void handleBootloaderOTAData(AsyncWebServerRequest *request, size_t index, uint8_t *data, size_t len, bool isFinal);
#endif

View File

@@ -613,6 +613,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
#ifndef WLED_DISABLE_OTA
aOtaEnabled = request->hasArg(F("AO"));
#endif
//createEditHandler(correctPIN && !otaLock);
otaSameSubnet = request->hasArg(F("SU"));
}
}
@@ -814,13 +815,8 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
}
}
strip.panel.shrink_to_fit(); // release unused memory
// we are changing matrix/ledmap geometry which *will* affect existing segments
// since we are not in loop() context we must make sure that effects are not running. credit @blazonchek for properly fixing #4911
strip.suspend();
strip.waitForIt();
strip.deserializeMap(); // (re)load default ledmap (will also setUpMatrix() if ledmap does not exist)
strip.makeAutoSegments(true); // force re-creation of segments
strip.resume();
}
#endif

View File

@@ -76,7 +76,7 @@ bool ESPAsyncE131::initMulticast(uint16_t port, uint16_t universe, uint8_t n) {
ip4_addr_t ifaddr;
ip4_addr_t multicast_addr;
ifaddr.addr = static_cast<uint32_t>(Network.localIP());
ifaddr.addr = static_cast<uint32_t>(WLEDNetwork.localIP());
for (uint8_t i = 1; i < n; i++) {
multicast_addr.addr = static_cast<uint32_t>(IPAddress(239, 255,
(((universe + i) >> 8) & 0xff), (((universe + i) >> 0)

View File

@@ -215,7 +215,7 @@ private:
void serveDescription()
{
EA_DEBUGLN("# Responding to description.xml ... #\n");
IPAddress localIP = Network.localIP();
IPAddress localIP = WLEDNetwork.localIP();
char s[16];
snprintf(s, sizeof(s), "%d.%d.%d.%d", localIP[0], localIP[1], localIP[2], localIP[3]);
char buf[1024];
@@ -289,7 +289,7 @@ private:
//respond to UDP SSDP M-SEARCH
void respondToSearch()
{
IPAddress localIP = Network.localIP();
IPAddress localIP = WLEDNetwork.localIP();
char s[16];
sprintf(s, "%d.%d.%d.%d", localIP[0], localIP[1], localIP[2], localIP[3]);
@@ -344,7 +344,7 @@ public:
#ifdef ARDUINO_ARCH_ESP32
udpConnected = espalexaUdp.beginMulticast(IPAddress(239, 255, 255, 250), 1900);
#else
udpConnected = espalexaUdp.beginMulticast(Network.localIP(), IPAddress(239, 255, 255, 250), 1900);
udpConnected = espalexaUdp.beginMulticast(WLEDNetwork.localIP(), IPAddress(239, 255, 255, 250), 1900);
#endif
if (udpConnected){

View File

@@ -1,6 +1,6 @@
#include "Network.h"
IPAddress NetworkClass::localIP()
IPAddress WLEDNetworkClass::localIP()
{
IPAddress localIP;
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
@@ -17,7 +17,7 @@ IPAddress NetworkClass::localIP()
return INADDR_NONE;
}
IPAddress NetworkClass::subnetMask()
IPAddress WLEDNetworkClass::subnetMask()
{
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
if (ETH.localIP()[0] != 0) {
@@ -30,7 +30,7 @@ IPAddress NetworkClass::subnetMask()
return IPAddress(255, 255, 255, 0);
}
IPAddress NetworkClass::gatewayIP()
IPAddress WLEDNetworkClass::gatewayIP()
{
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
if (ETH.localIP()[0] != 0) {
@@ -43,7 +43,7 @@ IPAddress NetworkClass::gatewayIP()
return INADDR_NONE;
}
void NetworkClass::localMAC(uint8_t* MAC)
void WLEDNetworkClass::localMAC(uint8_t* MAC)
{
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
// ETH.macAddress(MAC); // Does not work because of missing ETHClass:: in ETH.ccp
@@ -71,12 +71,12 @@ void NetworkClass::localMAC(uint8_t* MAC)
return;
}
bool NetworkClass::isConnected()
bool WLEDNetworkClass::isConnected()
{
return (WiFi.localIP()[0] != 0 && WiFi.status() == WL_CONNECTED) || isEthernet();
}
bool NetworkClass::isEthernet()
bool WLEDNetworkClass::isEthernet()
{
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
return (ETH.localIP()[0] != 0) && ETH.linkUp();
@@ -84,4 +84,4 @@ bool NetworkClass::isEthernet()
return false;
}
NetworkClass Network;
WLEDNetworkClass WLEDNetwork;

View File

@@ -8,7 +8,7 @@
#ifndef Network_h
#define Network_h
class NetworkClass
class WLEDNetworkClass
{
public:
IPAddress localIP();
@@ -19,6 +19,6 @@ public:
bool isEthernet();
};
extern NetworkClass Network;
extern WLEDNetworkClass WLEDNetwork;
#endif

View File

@@ -196,7 +196,7 @@ void notify(byte callMode, bool followUp)
#endif
{
DEBUG_PRINTLN(F("UDP sending packet."));
IPAddress broadcastIp = ~uint32_t(Network.subnetMask()) | uint32_t(Network.gatewayIP());
IPAddress broadcastIp = ~uint32_t(WLEDNetwork.subnetMask()) | uint32_t(WLEDNetwork.gatewayIP());
notifierUdp.beginPacket(broadcastIp, udpPort);
notifierUdp.write(udpOut, WLEDPACKETSIZE); // TODO: add actual used buffer size
notifierUdp.endPacket();
@@ -516,7 +516,7 @@ void handleNotifications()
}
}
localIP = Network.localIP();
localIP = WLEDNetwork.localIP();
//notifier and UDP realtime
if (!packetSize || packetSize > UDP_IN_MAXSIZE) return;
if (!isSupp && notifierUdp.remoteIP() == localIP) return; //don't process broadcasts we send ourselves
@@ -698,7 +698,7 @@ void sendSysInfoUDP()
{
if (!udp2Connected) return;
IPAddress ip = Network.localIP();
IPAddress ip = WLEDNetwork.localIP();
if (!ip || ip == IPAddress(255,255,255,255)) ip = IPAddress(4,3,2,1);
// TODO: make a nice struct of it and clean up

View File

@@ -3,7 +3,6 @@
#include "const.h"
#ifdef ESP8266
#include "user_interface.h" // for bootloop detection
#include <Hash.h> // for SHA1 on ESP8266
#else
#include <Update.h>
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)
@@ -11,8 +10,6 @@
#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(3, 3, 0)
#include "soc/rtc.h"
#endif
#include "mbedtls/sha1.h" // for SHA1 on ESP32
#include "esp_efuse.h"
#endif
@@ -372,6 +369,7 @@ void checkSettingsPIN(const char* pin) {
if (!correctPIN && millis() - lastEditTime < PIN_RETRY_COOLDOWN) return; // guard against PIN brute force
bool correctBefore = correctPIN;
correctPIN = (strlen(settingsPIN) == 0 || strncmp(settingsPIN, pin, 4) == 0);
if (correctBefore != correctPIN) createEditHandler(correctPIN);
lastEditTime = millis();
}
@@ -650,8 +648,7 @@ int32_t hw_random(int32_t lowerlimit, int32_t upperlimit) {
#ifdef ESP8266
static void *validateFreeHeap(void *buffer) {
// make sure there is enough free heap left if buffer was allocated in DRAM region, free it if not
// note: ESP826 needs very little contiguous heap for webserver, checking total free heap works better
if (getFreeHeapSize() < MIN_HEAP_SIZE) {
if (getContiguousFreeHeap() < MIN_HEAP_SIZE) {
free(buffer);
return nullptr;
}
@@ -1128,96 +1125,4 @@ uint8_t perlin8(uint16_t x, uint16_t y) {
uint8_t perlin8(uint16_t x, uint16_t y, uint16_t z) {
return (((perlin3D_raw((uint32_t)x << 8, (uint32_t)y << 8, (uint32_t)z << 8, true) * 2015) >> 10) + 33168) >> 8; //scale to 16 bit, offset, then scale to 8bit
}
// Platform-agnostic SHA1 computation from String input
String computeSHA1(const String& input) {
#ifdef ESP8266
return sha1(input); // ESP8266 has built-in sha1() function
#else
// ESP32: Compute SHA1 hash using mbedtls
unsigned char shaResult[20]; // SHA1 produces 20 bytes
mbedtls_sha1_context ctx;
mbedtls_sha1_init(&ctx);
mbedtls_sha1_starts_ret(&ctx);
mbedtls_sha1_update_ret(&ctx, (const unsigned char*)input.c_str(), input.length());
mbedtls_sha1_finish_ret(&ctx, shaResult);
mbedtls_sha1_free(&ctx);
// Convert to hexadecimal string
char hexString[41];
for (int i = 0; i < 20; i++) {
sprintf(&hexString[i*2], "%02x", shaResult[i]);
}
hexString[40] = '\0';
return String(hexString);
#endif
}
#ifdef ESP32
static String dump_raw_block(esp_efuse_block_t block)
{
const int WORDS = 8; // ESP32: 8×32-bit words per block i.e. 256bits
uint32_t buf[WORDS] = {0};
const esp_efuse_desc_t d = {
.efuse_block = block,
.bit_start = 0,
.bit_count = WORDS * 32
};
const esp_efuse_desc_t* field[2] = { &d, NULL };
esp_err_t err = esp_efuse_read_field_blob(field, buf, WORDS * 32);
if (err != ESP_OK) {
return "";
}
String result = "";
for (const unsigned int i : buf) {
char line[32];
sprintf(line, "0x%08X", i);
result += line;
}
return result;
}
#endif
// Generate a device ID based on SHA1 hash of MAC address salted with "WLED"
// Returns: original SHA1 + last 2 chars of double-hashed SHA1 (42 chars total)
String getDeviceId() {
static String cachedDeviceId = "";
if (cachedDeviceId.length() > 0) return cachedDeviceId;
uint8_t mac[6];
WiFi.macAddress(mac);
char macStr[18];
sprintf(macStr, "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
// The device string is deterministic as it needs to be consistent for the same device, even after a full flash erase
// MAC is salted with other consistent device info to avoid rainbow table attacks.
// If the MAC address is known by malicious actors, they could precompute SHA1 hashes to impersonate devices,
// but as WLED developers are just looking at statistics and not authenticating devices, this is acceptable.
// If the usage data was exfiltrated, you could not easily determine the MAC from the device ID without brute forcing SHA1
#ifdef ESP8266
String deviceString = String(macStr) + "WLED" + ESP.getFlashChipId();
#else
String deviceString = String(macStr) + "WLED" + ESP.getChipModel() + ESP.getChipRevision();
deviceString += dump_raw_block(EFUSE_BLK0);
deviceString += dump_raw_block(EFUSE_BLK1);
deviceString += dump_raw_block(EFUSE_BLK2);
deviceString += dump_raw_block(EFUSE_BLK3);
#endif
String firstHash = computeSHA1(deviceString);
// Second hash: SHA1 of the first hash
String secondHash = computeSHA1(firstHash);
// Concatenate first hash + last 2 chars of second hash
cachedDeviceId = firstHash + secondHash.substring(38);
return cachedDeviceId;
}
}

View File

@@ -1,7 +1,6 @@
#define WLED_DEFINE_GLOBAL_VARS //only in one source file, wled.cpp!
#include "wled.h"
#include "wled_ethernet.h"
#include "ota_update.h"
#ifdef WLED_ENABLE_AOTA
#define NO_OTA_PORT
#include <ArduinoOTA.h>
@@ -110,7 +109,7 @@ void WLED::loop()
{
if (apActive) dnsServer.processNextRequest();
#ifdef WLED_ENABLE_AOTA
if (Network.isConnected() && aOtaEnabled && !otaLock && correctPIN) ArduinoOTA.handle();
if (WLEDNetwork.isConnected() && aOtaEnabled && !otaLock && correctPIN) ArduinoOTA.handle();
#endif
handleNightlight();
yield();
@@ -167,15 +166,16 @@ void WLED::loop()
// 15min PIN time-out
if (strlen(settingsPIN)>0 && correctPIN && millis() - lastEditTime > PIN_TIMEOUT) {
correctPIN = false;
createEditHandler(false);
}
// reconnect WiFi to clear stale allocations if heap gets too low
if (millis() - heapTime > 15000) {
uint32_t heap = getFreeHeapSize();
if (heap < MIN_HEAP_SIZE && lastHeap < MIN_HEAP_SIZE) {
DEBUG_PRINTF_P(PSTR("Heap too low! %u\n"), heap);
DEBUG_PRINTF_P(PSTR("Heap too low! %u\n"), heap);
forceReconnect = true;
strip.resetSegments(); // remove all but one segments from memory
if (!Update.isRunning()) forceReconnect = true;
} else if (heap < MIN_HEAP_SIZE) {
DEBUG_PRINTLN(F("Heap low, purging segments."));
strip.purgeSegments();
@@ -284,7 +284,7 @@ void WLED::loop()
lastWifiState = WiFi.status();
DEBUG_PRINTF_P(PSTR("State time: %lu\n"), wifiStateChangedTime);
DEBUG_PRINTF_P(PSTR("NTP last sync: %lu\n"), ntpLastSyncTime);
DEBUG_PRINTF_P(PSTR("Client IP: %u.%u.%u.%u\n"), Network.localIP()[0], Network.localIP()[1], Network.localIP()[2], Network.localIP()[3]);
DEBUG_PRINTF_P(PSTR("Client IP: %u.%u.%u.%u\n"), WLEDNetwork.localIP()[0], WLEDNetwork.localIP()[1], WLEDNetwork.localIP()[2], WLEDNetwork.localIP()[3]);
if (loops > 0) { // avoid division by zero
DEBUG_PRINTF_P(PSTR("Loops/sec: %u\n"), loops / 30);
DEBUG_PRINTF_P(PSTR("Loop time[ms]: %u/%lu\n"), avgLoopMillis/loops, maxLoopMillis);
@@ -555,7 +555,6 @@ void WLED::setup()
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DISABLE_BROWNOUT_DET)
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 1); //enable brownout detector
#endif
markOTAvalid();
}
void WLED::beginStrip()
@@ -659,11 +658,17 @@ void WLED::initConnection()
WiFi.setPhyMode(force802_3g ? WIFI_PHY_MODE_11G : WIFI_PHY_MODE_11N);
#endif
if (multiWiFi[selectedWiFi].staticIP != 0U && multiWiFi[selectedWiFi].staticGW != 0U) {
if (multiWiFi.empty()) { // guard: handle empty WiFi list safely
WiFi.config(IPAddress((uint32_t)0), IPAddress((uint32_t)0), IPAddress((uint32_t)0));
} else {
if (selectedWiFi >= multiWiFi.size()) selectedWiFi = 0; // guard: ensure valid index
if (multiWiFi[selectedWiFi].staticIP != IPAddress((uint32_t)0) &&
multiWiFi[selectedWiFi].staticGW != IPAddress((uint32_t)0)) { // guard: compare as IPAddress to avoid pointer overload
WiFi.config(multiWiFi[selectedWiFi].staticIP, multiWiFi[selectedWiFi].staticGW, multiWiFi[selectedWiFi].staticSN, dnsAddress);
} else {
WiFi.config(IPAddress((uint32_t)0), IPAddress((uint32_t)0), IPAddress((uint32_t)0));
}
}
lastReconnectAttempt = millis();
@@ -727,7 +732,7 @@ void WLED::initInterfaces()
DEBUG_PRINTLN(F("Init STA interfaces"));
#ifndef WLED_DISABLE_HUESYNC
IPAddress ipAddress = Network.localIP();
IPAddress ipAddress = WLEDNetwork.localIP();
if (hueIP[0] == 0) {
hueIP[0] = ipAddress[0];
hueIP[1] = ipAddress[1];
@@ -813,7 +818,7 @@ void WLED::handleConnection()
if (stac != stacO) {
stacO = stac;
DEBUG_PRINTF_P(PSTR("Connected AP clients: %d\n"), (int)stac);
if (!Network.isConnected() && wifiConfigured) { // trying to connect, but not connected
if (!WLEDNetwork.isConnected() && wifiConfigured) { // trying to connect, but not connected
if (stac)
WiFi.disconnect(); // disable search so that AP can work
else
@@ -822,7 +827,7 @@ void WLED::handleConnection()
}
}
if (!Network.isConnected()) {
if (!WLEDNetwork.isConnected()) {
if (interfacesInited) {
if (scanDone && multiWiFi.size() > 1) {
DEBUG_PRINTLN(F("WiFi scan initiated on disconnect."));
@@ -866,7 +871,7 @@ void WLED::handleConnection()
} else if (!interfacesInited) { //newly connected
DEBUG_PRINTLN();
DEBUG_PRINT(F("Connected! IP address: "));
DEBUG_PRINTLN(Network.localIP());
DEBUG_PRINTLN(WLEDNetwork.localIP());
if (improvActive) {
if (improvError == 3) sendImprovStateResponse(0x00, true);
sendImprovStateResponse(0x04);
@@ -888,7 +893,7 @@ void WLED::handleConnection()
}
// If status LED pin is allocated for other uses, does nothing
// else blink at 1Hz when Network.isConnected() is false (no WiFi, ?? no Ethernet ??)
// else blink at 1Hz when WLEDNetwork.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)
@@ -902,7 +907,7 @@ void WLED::handleStatusLED()
}
#endif
if (Network.isConnected()) {
if (WLEDNetwork.isConnected()) {
c = RGBW32(0,255,0,0);
ledStatusType = 2;
} else if (WLED_MQTT_CONNECTED) {

View File

@@ -189,15 +189,11 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument<PSRAM_Allocator>;
#include "FastLED.h"
#include "const.h"
#include "fcn_declare.h"
#ifndef WLED_DISABLE_OTA
#include "ota_update.h"
#endif
#include "NodeStruct.h"
#include "pin_manager.h"
#include "colors.h"
#include "bus_manager.h"
#include "FX.h"
#include "wled_metadata.h"
#ifndef CLIENT_SSID
#define CLIENT_SSID DEFAULT_CLIENT_SSID
@@ -274,6 +270,20 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument<PSRAM_Allocator>;
#define STRINGIFY(X) #X
#define TOSTRING(X) STRINGIFY(X)
#ifndef WLED_VERSION
#define WLED_VERSION dev
#endif
#ifndef WLED_RELEASE_NAME
#define WLED_RELEASE_NAME "Custom"
#endif
#ifndef WLED_REPO
#define WLED_REPO "unknown"
#endif
// Global Variable definitions
WLED_GLOBAL char versionString[] _INIT(TOSTRING(WLED_VERSION));
WLED_GLOBAL char releaseString[] _INIT(WLED_RELEASE_NAME); // must include the quotes when defining, e.g -D WLED_RELEASE_NAME=\"ESP32_MULTI_USREMODS\"
WLED_GLOBAL char repoString[] _INIT(WLED_REPO);
#define WLED_CODENAME "Niji"
// AP and OTA default passwords (for maximum security change them!)
@@ -1018,7 +1028,7 @@ WLED_GLOBAL volatile uint8_t jsonBufferLock _INIT(0);
WLED_GLOBAL unsigned loops _INIT(0);
#endif
#define WLED_CONNECTED (Network.isConnected())
#define WLED_CONNECTED (WLEDNetwork.isConnected())
#ifndef WLED_AP_SSID_UNIQUE
#define WLED_SET_AP_SSID() do { \

View File

@@ -1,164 +0,0 @@
#include "ota_update.h"
#include "wled.h"
#include "wled_metadata.h"
#ifndef WLED_VERSION
#warning WLED_VERSION was not set - using default value of 'dev'
#define WLED_VERSION dev
#endif
#ifndef WLED_RELEASE_NAME
#warning WLED_RELEASE_NAME was not set - using default value of 'Custom'
#define WLED_RELEASE_NAME "Custom"
#endif
#ifndef WLED_REPO
// No warning for this one: integrators are not always on GitHub
#define WLED_REPO "unknown"
#endif
constexpr uint32_t WLED_CUSTOM_DESC_MAGIC = 0x57535453; // "WSTS" (WLED System Tag Structure)
constexpr uint32_t WLED_CUSTOM_DESC_VERSION = 1;
// Compile-time validation that release name doesn't exceed maximum length
static_assert(sizeof(WLED_RELEASE_NAME) <= WLED_RELEASE_NAME_MAX_LEN,
"WLED_RELEASE_NAME exceeds maximum length of WLED_RELEASE_NAME_MAX_LEN characters");
/**
* DJB2 hash function (C++11 compatible constexpr)
* Used for compile-time hash computation to validate structure contents
* Recursive for compile time: not usable at runtime due to stack depth
*
* Note that this only works on strings; there is no way to produce a compile-time
* hash of a struct in C++11 without explicitly listing all the struct members.
* So for now, we hash only the release name. This suffices for a "did you find
* valid structure" check.
*
*/
constexpr uint32_t djb2_hash_constexpr(const char* str, uint32_t hash = 5381) {
return (*str == '\0') ? hash : djb2_hash_constexpr(str + 1, ((hash << 5) + hash) + *str);
}
/**
* Runtime DJB2 hash function for validation
*/
inline uint32_t djb2_hash_runtime(const char* str) {
uint32_t hash = 5381;
while (*str) {
hash = ((hash << 5) + hash) + *str++;
}
return hash;
}
// ------------------------------------
// GLOBAL VARIABLES
// ------------------------------------
// Structure instantiation for this build
const wled_metadata_t __attribute__((section(BUILD_METADATA_SECTION))) WLED_BUILD_DESCRIPTION = {
WLED_CUSTOM_DESC_MAGIC, // magic
WLED_CUSTOM_DESC_VERSION, // version
TOSTRING(WLED_VERSION),
WLED_RELEASE_NAME, // release_name
std::integral_constant<uint32_t, djb2_hash_constexpr(WLED_RELEASE_NAME)>::value, // hash - computed at compile time; integral_constant enforces this
};
static const char repoString_s[] PROGMEM = WLED_REPO;
const __FlashStringHelper* repoString = FPSTR(repoString_s);
static const char productString_s[] PROGMEM = WLED_PRODUCT_NAME;
const __FlashStringHelper* productString = FPSTR(productString_s);
static const char brandString_s [] PROGMEM = WLED_BRAND;
const __FlashStringHelper* brandString = FPSTR(brandString_s);
/**
* Extract WLED custom description structure from binary
* @param binaryData Pointer to binary file data
* @param dataSize Size of binary data in bytes
* @param extractedDesc Buffer to store extracted custom description structure
* @return true if structure was found and extracted, false otherwise
*/
bool findWledMetadata(const uint8_t* binaryData, size_t dataSize, wled_metadata_t* extractedDesc) {
if (!binaryData || !extractedDesc || dataSize < sizeof(wled_metadata_t)) {
return false;
}
for (size_t offset = 0; offset <= dataSize - sizeof(wled_metadata_t); offset++) {
if ((binaryData[offset]) == static_cast<char>(WLED_CUSTOM_DESC_MAGIC)) {
// First byte matched; check next in an alignment-safe way
uint32_t data_magic;
memcpy(&data_magic, binaryData + offset, sizeof(data_magic));
// Check for magic number
if (data_magic == WLED_CUSTOM_DESC_MAGIC) {
wled_metadata_t candidate;
memcpy(&candidate, binaryData + offset, sizeof(candidate));
// Found potential match, validate version
if (candidate.desc_version != WLED_CUSTOM_DESC_VERSION) {
DEBUG_PRINTF_P(PSTR("Found WLED structure at offset %u but version mismatch: %u\n"),
offset, candidate.desc_version);
continue;
}
// Validate hash using runtime function
uint32_t expected_hash = djb2_hash_runtime(candidate.release_name);
if (candidate.hash != expected_hash) {
DEBUG_PRINTF_P(PSTR("Found WLED structure at offset %u but hash mismatch\n"), offset);
continue;
}
// Valid structure found - copy entire structure
*extractedDesc = candidate;
DEBUG_PRINTF_P(PSTR("Extracted WLED structure at offset %u: '%s'\n"),
offset, extractedDesc->release_name);
return true;
}
}
}
DEBUG_PRINTLN(F("No WLED custom description found in binary"));
return false;
}
/**
* Check if OTA should be allowed based on release compatibility using custom description
* @param binaryData Pointer to binary file data (not modified)
* @param dataSize Size of binary data in bytes
* @param errorMessage Buffer to store error message if validation fails
* @param errorMessageLen Maximum length of error message buffer
* @return true if OTA should proceed, false if it should be blocked
*/
bool shouldAllowOTA(const wled_metadata_t& firmwareDescription, char* errorMessage, size_t errorMessageLen) {
// Clear error message
if (errorMessage && errorMessageLen > 0) {
errorMessage[0] = '\0';
}
// Validate compatibility using extracted release name
// We make a stack copy so we can print it safely
char safeFirmwareRelease[WLED_RELEASE_NAME_MAX_LEN];
strncpy(safeFirmwareRelease, firmwareDescription.release_name, WLED_RELEASE_NAME_MAX_LEN - 1);
safeFirmwareRelease[WLED_RELEASE_NAME_MAX_LEN - 1] = '\0';
if (strlen(safeFirmwareRelease) == 0) {
return false;
}
if (strncmp_P(safeFirmwareRelease, releaseString, WLED_RELEASE_NAME_MAX_LEN) != 0) {
if (errorMessage && errorMessageLen > 0) {
snprintf_P(errorMessage, errorMessageLen, PSTR("Firmware compatibility mismatch: current='%s', uploaded='%s'."),
releaseString, safeFirmwareRelease);
errorMessage[errorMessageLen - 1] = '\0'; // Ensure null termination
}
return false;
}
// TODO: additional checks go here
return true;
}

View File

@@ -1,61 +0,0 @@
/*
WLED build metadata
Manages and exports information about the current WLED build.
*/
#pragma once
#include <cstdint>
#include <string.h>
#include <WString.h>
#define WLED_VERSION_MAX_LEN 48
#define WLED_RELEASE_NAME_MAX_LEN 48
/**
* WLED Custom Description Structure
* This structure is embedded in platform-specific sections at an approximately
* fixed offset in ESP32/ESP8266 binaries, where it can be found and validated
* by the OTA process.
*/
typedef struct {
uint32_t magic; // Magic number to identify WLED custom description
uint32_t desc_version; // Structure version for future compatibility
char wled_version[WLED_VERSION_MAX_LEN];
char release_name[WLED_RELEASE_NAME_MAX_LEN]; // Release name (null-terminated)
uint32_t hash; // Structure sanity check
} __attribute__((packed)) wled_metadata_t;
// Global build description
extern const wled_metadata_t WLED_BUILD_DESCRIPTION;
// Convenient metdata pointers
#define versionString (WLED_BUILD_DESCRIPTION.wled_version) // Build version, WLED_VERSION
#define releaseString (WLED_BUILD_DESCRIPTION.release_name) // Release name, WLED_RELEASE_NAME
extern const __FlashStringHelper* repoString; // Github repository (if available)
extern const __FlashStringHelper* productString; // Product, WLED_PRODUCT_NAME -- deprecated, use WLED_RELEASE_NAME
extern const __FlashStringHelper* brandString ; // Brand
// Metadata analysis functions
/**
* Extract WLED custom description structure from binary data
* @param binaryData Pointer to binary file data
* @param dataSize Size of binary data in bytes
* @param extractedDesc Buffer to store extracted custom description structure
* @return true if structure was found and extracted, false otherwise
*/
bool findWledMetadata(const uint8_t* binaryData, size_t dataSize, wled_metadata_t* extractedDesc);
/**
* Check if OTA should be allowed based on release compatibility
* @param firmwareDescription Pointer to firmware description
* @param errorMessage Buffer to store error message if validation fails
* @param errorMessageLen Maximum length of error message buffer
* @return true if OTA should proceed, false if it should be blocked
*/
bool shouldAllowOTA(const wled_metadata_t& firmwareDescription, char* errorMessage, size_t errorMessageLen);

View File

@@ -1,7 +1,11 @@
#include "wled.h"
#ifndef WLED_DISABLE_OTA
#include "ota_update.h"
#ifdef ESP8266
#include <Updater.h>
#else
#include <Update.h>
#endif
#endif
#include "html_ui.h"
#include "html_settings.h"
@@ -13,8 +17,6 @@
#include "html_pxmagic.h"
#endif
#include "html_cpal.h"
#include "html_edit.h"
// define flash strings once (saves flash memory)
static const char s_redirecting[] PROGMEM = "Redirecting...";
@@ -24,16 +26,8 @@ static const char s_unlock_cfg [] PROGMEM = "Please unlock settings using PIN co
static const char s_rebooting [] PROGMEM = "Rebooting now...";
static const char s_notimplemented[] PROGMEM = "Not implemented";
static const char s_accessdenied[] PROGMEM = "Access Denied";
static const char s_not_found[] PROGMEM = "Not found";
static const char s_wsec[] PROGMEM = "wsec.json";
static const char s_func[] PROGMEM = "func";
static const char s_path[] PROGMEM = "path";
static const char s_cache_control[] PROGMEM = "Cache-Control";
static const char s_no_store[] PROGMEM = "no-store";
static const char s_expires[] PROGMEM = "Expires";
static const char _common_js[] PROGMEM = "/common.js";
//Is this an IP?
static bool isIp(const String &str) {
for (size_t i = 0; i < str.length(); i++) {
@@ -50,7 +44,7 @@ static bool inSubnet(const IPAddress &ip, const IPAddress &subnet, const IPAddre
}
static bool inSameSubnet(const IPAddress &client) {
return inSubnet(client, Network.localIP(), Network.subnetMask());
return inSubnet(client, WLEDNetwork.localIP(), WLEDNetwork.subnetMask());
}
static bool inLocalSubnet(const IPAddress &client) {
@@ -77,9 +71,9 @@ static void setStaticContentCacheHeaders(AsyncWebServerResponse *response, int c
#ifndef WLED_DEBUG
// this header name is misleading, "no-cache" will not disable cache,
// it just revalidates on every load using the "If-None-Match" header with the last ETag value
response->addHeader(FPSTR(s_cache_control), F("no-cache"));
response->addHeader(F("Cache-Control"), F("no-cache"));
#else
response->addHeader(FPSTR(s_cache_control), F("no-store,max-age=0")); // prevent caching if debug build
response->addHeader(F("Cache-Control"), F("no-store,max-age=0")); // prevent caching if debug build
#endif
char etag[32];
generateEtag(etag, eTagSuffix);
@@ -182,7 +176,6 @@ static String msgProcessor(const String& var)
return String();
}
static void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool isFinal) {
if (!correctPIN) {
if (isFinal) request->send(401, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_unlock_cfg));
@@ -205,7 +198,7 @@ static void handleUpload(AsyncWebServerRequest *request, const String& filename,
request->_tempFile.close();
if (filename.indexOf(F("cfg.json")) >= 0) { // check for filename with or without slash
doReboot = true;
request->send(200, FPSTR(CONTENT_TYPE_PLAIN), F("Config restore ok.\nRebooting..."));
request->send(200, FPSTR(CONTENT_TYPE_PLAIN), F("Configuration restore successful.\nRebooting..."));
} else {
if (filename.indexOf(F("palette")) >= 0 && filename.indexOf(F(".json")) >= 0) loadCustomPalettes();
request->send(200, FPSTR(CONTENT_TYPE_PLAIN), F("File Uploaded!"));
@@ -214,94 +207,25 @@ static void handleUpload(AsyncWebServerRequest *request, const String& filename,
}
}
static const char _edit_htm[] PROGMEM = "/edit.htm";
void createEditHandler() {
void createEditHandler(bool enable) {
if (editHandler != nullptr) server.removeHandler(editHandler);
editHandler = &server.on(F("/edit"), static_cast<WebRequestMethod>(HTTP_GET), [](AsyncWebServerRequest *request) {
// PIN check for GET/DELETE, for POST it is done in handleUpload()
if (!correctPIN) {
if (enable) {
#ifdef WLED_ENABLE_FS_EDITOR
#ifdef ARDUINO_ARCH_ESP32
editHandler = &server.addHandler(new SPIFFSEditor(WLED_FS));//http_username,http_password));
#else
editHandler = &server.addHandler(new SPIFFSEditor("","",WLED_FS));//http_username,http_password));
#endif
#else
editHandler = &server.on(F("/edit"), HTTP_GET, [](AsyncWebServerRequest *request){
serveMessage(request, 501, FPSTR(s_notimplemented), F("The FS editor is disabled in this build."), 254);
});
#endif
} else {
editHandler = &server.on(F("/edit"), HTTP_ANY, [](AsyncWebServerRequest *request){
serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_cfg), 254);
return;
}
const String& func = request->arg(FPSTR(s_func));
if(func.length() == 0) {
// default: serve the editor page
handleStaticContent(request, FPSTR(_edit_htm), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_edit, PAGE_edit_length);
return;
}
if (func == "list") {
bool first = true;
AsyncResponseStream* response = request->beginResponseStream(FPSTR(CONTENT_TYPE_JSON));
response->addHeader(FPSTR(s_cache_control), FPSTR(s_no_store));
response->addHeader(FPSTR(s_expires), F("0"));
response->write('[');
File rootdir = WLED_FS.open("/", "r");
File rootfile = rootdir.openNextFile();
while (rootfile) {
String name = rootfile.name();
if (name.indexOf(FPSTR(s_wsec)) >= 0) {
rootfile = rootdir.openNextFile(); // skip wsec.json
continue;
}
if (!first) response->write(',');
first = false;
response->printf_P(PSTR("{\"name\":\"%s\",\"type\":\"file\",\"size\":%u}"), name.c_str(), rootfile.size());
rootfile = rootdir.openNextFile();
}
rootfile.close();
rootdir.close();
response->write(']');
request->send(response);
return;
}
String path = request->arg(FPSTR(s_path)); // remaining functions expect a path
if (path.length() == 0) {
request->send(400, FPSTR(CONTENT_TYPE_PLAIN), F("Missing path"));
return;
}
if (path.charAt(0) != '/') {
path = '/' + path; // prepend slash if missing
}
if (!WLED_FS.exists(path)) {
request->send(404, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_not_found));
return;
}
if (path.indexOf(FPSTR(s_wsec)) >= 0) {
request->send(403, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_accessdenied)); // skip wsec.json
return;
}
if (func == "edit") {
request->send(WLED_FS, path);
return;
}
if (func == "download") {
request->send(WLED_FS, path, String(), true);
return;
}
if (func == "delete") {
if (!WLED_FS.remove(path))
request->send(500, FPSTR(CONTENT_TYPE_PLAIN), F("Delete failed"));
else
request->send(200, FPSTR(CONTENT_TYPE_PLAIN), F("File deleted"));
return;
}
// unrecognized func
request->send(400, FPSTR(CONTENT_TYPE_PLAIN), F("Invalid function"));
});
});
}
}
static bool captivePortal(AsyncWebServerRequest *request)
@@ -467,7 +391,7 @@ void initServer()
size_t len, bool isFinal) {handleUpload(request, filename, index, data, len, isFinal);}
);
createEditHandler(); // initialize "/edit" handler, access is protected by "correctPIN"
createEditHandler(correctPIN);
static const char _update[] PROGMEM = "/update";
#ifndef WLED_DISABLE_OTA
@@ -480,47 +404,59 @@ void initServer()
});
server.on(_update, HTTP_POST, [](AsyncWebServerRequest *request){
if (request->_tempObject) {
auto ota_result = getOTAResult(request);
if (ota_result.first) {
if (ota_result.second.length() > 0) {
serveMessage(request, 500, F("Update failed!"), ota_result.second, 254);
} else {
serveMessage(request, 200, F("Update successful!"), FPSTR(s_rebooting), 131);
}
}
if (!correctPIN) {
serveSettings(request, true); // handle PIN page POST request
return;
}
if (otaLock) {
serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_ota), 254);
return;
}
if (Update.hasError()) {
serveMessage(request, 500, F("Update failed!"), F("Please check your file and retry!"), 254);
} else {
// No context structure - something's gone horribly wrong
serveMessage(request, 500, F("Update failed!"), F("Internal server fault"), 254);
serveMessage(request, 200, F("Update successful!"), FPSTR(s_rebooting), 131);
#ifndef ESP8266
bootloopCheckOTA(); // let the bootloop-checker know there was an OTA update
#endif
doReboot = true;
}
},[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool isFinal){
if (index == 0) {
// Allocate the context structure
if (!initOTA(request)) {
return; // Error will be dealt with after upload in response handler, above
}
// Privilege checks
IPAddress client = request->client()->remoteIP();
if (((otaSameSubnet && !inSameSubnet(client)) && !strlen(settingsPIN)) || (!otaSameSubnet && !inLocalSubnet(client))) {
DEBUG_PRINTLN(F("Attempted OTA update from different/non-local subnet!"));
serveMessage(request, 401, FPSTR(s_accessdenied), F("Client is not on local subnet."), 254);
setOTAReplied(request);
return;
}
if (!correctPIN) {
serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_cfg), 254);
setOTAReplied(request);
return;
};
if (otaLock) {
serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_ota), 254);
setOTAReplied(request);
return;
}
IPAddress client = request->client()->remoteIP();
if (((otaSameSubnet && !inSameSubnet(client)) && !strlen(settingsPIN)) || (!otaSameSubnet && !inLocalSubnet(client))) {
DEBUG_PRINTLN(F("Attempted OTA update from different/non-local subnet!"));
request->send(401, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_accessdenied));
return;
}
if (!correctPIN || otaLock) return;
if(!index){
DEBUG_PRINTLN(F("OTA Update Start"));
#if WLED_WATCHDOG_TIMEOUT > 0
WLED::instance().disableWatchdog();
#endif
UsermodManager::onUpdateBegin(true); // notify usermods that update is about to begin (some may require task de-init)
lastEditTime = millis(); // make sure PIN does not lock during update
strip.suspend();
backupConfig(); // backup current config in case the update ends badly
strip.resetSegments(); // free as much memory as you can
#ifdef ESP8266
Update.runAsync(true);
#endif
Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000);
}
if(!Update.hasError()) Update.write(data, len);
if(isFinal){
if(Update.end(true)){
DEBUG_PRINTLN(F("Update Success"));
} else {
DEBUG_PRINTLN(F("Update Failed"));
strip.resume();
UsermodManager::onUpdateBegin(false); // notify usermods that update has failed (some may require task init)
#if WLED_WATCHDOG_TIMEOUT > 0
WLED::instance().enableWatchdog();
#endif
}
}
handleOTAData(request, index, data, len, isFinal);
});
#else
const auto notSupported = [](AsyncWebServerRequest *request){
@@ -530,53 +466,6 @@ void initServer()
server.on(_update, HTTP_POST, notSupported, [](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool isFinal){});
#endif
#if defined(ARDUINO_ARCH_ESP32) && !defined(WLED_DISABLE_OTA)
// ESP32 bootloader update endpoint
server.on(F("/updatebootloader"), HTTP_POST, [](AsyncWebServerRequest *request){
if (request->_tempObject) {
auto bootloader_result = getBootloaderOTAResult(request);
if (bootloader_result.first) {
if (bootloader_result.second.length() > 0) {
serveMessage(request, 500, F("Bootloader update failed!"), bootloader_result.second, 254);
} else {
serveMessage(request, 200, F("Bootloader updated successfully!"), FPSTR(s_rebooting), 131);
}
}
} else {
// No context structure - something's gone horribly wrong
serveMessage(request, 500, F("Bootloader update failed!"), F("Internal server fault"), 254);
}
},[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool isFinal){
if (index == 0) {
// Privilege checks
IPAddress client = request->client()->remoteIP();
if (((otaSameSubnet && !inSameSubnet(client)) && !strlen(settingsPIN)) || (!otaSameSubnet && !inLocalSubnet(client))) {
DEBUG_PRINTLN(F("Attempted bootloader update from different/non-local subnet!"));
serveMessage(request, 401, FPSTR(s_accessdenied), F("Client is not on local subnet."), 254);
setBootloaderOTAReplied(request);
return;
}
if (!correctPIN) {
serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_cfg), 254);
setBootloaderOTAReplied(request);
return;
}
if (otaLock) {
serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_ota), 254);
setBootloaderOTAReplied(request);
return;
}
// Allocate the context structure
if (!initBootloaderOTA(request)) {
return; // Error will be dealt with after upload in response handler, above
}
}
handleBootloaderOTAData(request, index, data, len, 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);
@@ -680,8 +569,8 @@ void serveSettingsJS(AsyncWebServerRequest* request)
}
AsyncResponseStream *response = request->beginResponseStream(FPSTR(CONTENT_TYPE_JAVASCRIPT));
response->addHeader(FPSTR(s_cache_control), FPSTR(s_no_store));
response->addHeader(FPSTR(s_expires), F("0"));
response->addHeader(F("Cache-Control"), F("no-store"));
response->addHeader(F("Expires"), F("0"));
response->print(F("function GetV(){var d=document;"));
getSettingsJS(subPage, *response);
@@ -805,6 +694,7 @@ void serveSettings(AsyncWebServerRequest* request, bool post) {
#endif
case SUBPAGE_LOCK : {
correctPIN = !strlen(settingsPIN); // lock if a pin is set
createEditHandler(correctPIN);
serveMessage(request, 200, strlen(settingsPIN) > 0 ? PSTR("Settings locked") : PSTR("No PIN set"), FPSTR(s_redirecting), 1);
return;
}

View File

@@ -251,14 +251,14 @@ void getSettingsJS(byte subPage, Print& settingsScript)
settingsScript.print(F("gId('ethd').style.display='none';"));
#endif
if (Network.isConnected()) //is connected
if (WLEDNetwork.isConnected()) //is connected
{
char s[32];
IPAddress localIP = Network.localIP();
IPAddress localIP = WLEDNetwork.localIP();
sprintf(s, "%d.%d.%d.%d", localIP[0], localIP[1], localIP[2], localIP[3]);
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
if (Network.isEthernet()) strcat_P(s ,PSTR(" (Ethernet)"));
if (WLEDNetwork.isEthernet()) strcat_P(s ,PSTR(" (Ethernet)"));
#endif
printSetClassElementHTML(settingsScript,PSTR("sip"),0,s);
} else
@@ -671,6 +671,16 @@ void getSettingsJS(byte subPage, Print& settingsScript)
UsermodManager::appendConfigData(settingsScript);
}
if (subPage == SUBPAGE_UPDATE) // update
{
char tmp_buf[128];
fillWLEDVersion(tmp_buf,sizeof(tmp_buf));
printSetClassElementHTML(settingsScript,PSTR("sip"),0,tmp_buf);
#ifndef ARDUINO_ARCH_ESP32
settingsScript.print(F("toggle('rev');")); // hide revert button on ESP8266
#endif
}
if (subPage == SUBPAGE_2D) // 2D matrices
{
printSetFormValue(settingsScript,PSTR("SOMP"),strip.isMatrix);