mirror of
https://github.com/wled/WLED.git
synced 2025-11-11 03:51:30 +00:00
Compare commits
90 Commits
copilot/ad
...
nightly
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd933ff230 | ||
|
|
7addae9c24 | ||
|
|
a73a2aaa33 | ||
|
|
7aedf77d83 | ||
|
|
50d33c5bf4 | ||
|
|
c7c379f962 | ||
|
|
af8c851cc6 | ||
|
|
88466c7d1f | ||
|
|
a36638ee6d | ||
|
|
ff93a48926 | ||
|
|
9474c29946 | ||
|
|
8097c7c86d | ||
|
|
abfe91d47b | ||
|
|
a4109c7ea8 | ||
|
|
34445dbe0f | ||
|
|
c5631b8fe3 | ||
|
|
4cddd3face | ||
|
|
5250a0fe2c | ||
|
|
95611f19c0 | ||
|
|
b98ee3e7b6 | ||
|
|
90d4dd79de | ||
|
|
00904d8862 | ||
|
|
2e7b6b79bf | ||
|
|
104d2ae7e8 | ||
|
|
f1242bfb7a | ||
|
|
aef5e9691c | ||
|
|
3d9012b43a | ||
|
|
3c5df5ae66 | ||
|
|
520f1f884b | ||
|
|
8fc33fd7b1 | ||
|
|
9e0f7ec4e9 | ||
|
|
00ca694eea | ||
|
|
c91a39f55c | ||
|
|
ffc7b66c20 | ||
|
|
94bea4405a | ||
|
|
2963c1b761 | ||
|
|
013ecfb189 | ||
|
|
670f74d589 | ||
|
|
d475d21a79 | ||
|
|
91349234a0 | ||
|
|
601bb6f0ca | ||
|
|
07e26d31f4 | ||
|
|
151acb249e | ||
|
|
25d5295d5d | ||
|
|
474a995845 | ||
|
|
f0f12e77ad | ||
|
|
46125773d9 | ||
|
|
ec61a35042 | ||
|
|
1afd72cb83 | ||
|
|
c623b82698 | ||
|
|
acd415c522 | ||
|
|
5fb37130f8 | ||
|
|
8e00e7175c | ||
|
|
0f06535932 | ||
|
|
46ff43889b | ||
|
|
7e1992fc5c | ||
|
|
0391488cef | ||
|
|
eb80fdf733 | ||
|
|
4973fd5a39 | ||
|
|
1dd338c5e0 | ||
|
|
186c4a7724 | ||
|
|
f0182eb1b2 | ||
|
|
2acf731baf | ||
|
|
e2b8f91417 | ||
|
|
5f33c69dd0 | ||
|
|
76bb3f7d77 | ||
|
|
da1d53c3b0 | ||
|
|
f4b98c43de | ||
|
|
62c78fc5ac | ||
|
|
9c4cf78a52 | ||
|
|
80c97076ae | ||
|
|
c3f394489f | ||
|
|
ce172df91a | ||
|
|
91baa34071 | ||
|
|
1da2692c34 | ||
|
|
d538736411 | ||
|
|
b268aea0ab | ||
|
|
a04d70293d | ||
|
|
c66d67dd19 | ||
|
|
0c22163fd9 | ||
|
|
50c0f41508 | ||
|
|
5ca10f35d1 | ||
|
|
a073bf32e4 | ||
|
|
d79b02379e | ||
|
|
f5f3fc338f | ||
|
|
042ed39464 | ||
|
|
c3e18905c1 | ||
|
|
a18a661c73 | ||
|
|
93908e758f | ||
|
|
30fbf55b9a |
@@ -2,6 +2,7 @@ Import('env')
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import gzip
|
import gzip
|
||||||
|
import json
|
||||||
|
|
||||||
OUTPUT_DIR = "build_output{}".format(os.path.sep)
|
OUTPUT_DIR = "build_output{}".format(os.path.sep)
|
||||||
#OUTPUT_DIR = os.path.join("build_output")
|
#OUTPUT_DIR = os.path.join("build_output")
|
||||||
@@ -22,7 +23,8 @@ def create_release(source):
|
|||||||
release_name_def = _get_cpp_define_value(env, "WLED_RELEASE_NAME")
|
release_name_def = _get_cpp_define_value(env, "WLED_RELEASE_NAME")
|
||||||
if release_name_def:
|
if release_name_def:
|
||||||
release_name = release_name_def.replace("\\\"", "")
|
release_name = release_name_def.replace("\\\"", "")
|
||||||
version = _get_cpp_define_value(env, "WLED_VERSION")
|
with open("package.json", "r") as package:
|
||||||
|
version = json.load(package)["version"]
|
||||||
release_file = os.path.join(OUTPUT_DIR, "release", f"WLED_{version}_{release_name}.bin")
|
release_file = os.path.join(OUTPUT_DIR, "release", f"WLED_{version}_{release_name}.bin")
|
||||||
release_gz_file = release_file + ".gz"
|
release_gz_file = release_file + ".gz"
|
||||||
print(f"Copying {source} to {release_file}")
|
print(f"Copying {source} to {release_file}")
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
Import('env')
|
Import('env')
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import json
|
||||||
import re
|
import re
|
||||||
|
|
||||||
def get_github_repo():
|
def get_github_repo():
|
||||||
@@ -42,7 +43,7 @@ def get_github_repo():
|
|||||||
|
|
||||||
# Check if it's a GitHub URL
|
# Check if it's a GitHub URL
|
||||||
if 'github.com' not in remote_url.lower():
|
if 'github.com' not in remote_url.lower():
|
||||||
return 'unknown'
|
return None
|
||||||
|
|
||||||
# Parse GitHub URL patterns:
|
# Parse GitHub URL patterns:
|
||||||
# https://github.com/owner/repo.git
|
# https://github.com/owner/repo.git
|
||||||
@@ -63,17 +64,53 @@ def get_github_repo():
|
|||||||
if ssh_match:
|
if ssh_match:
|
||||||
return ssh_match.group(1)
|
return ssh_match.group(1)
|
||||||
|
|
||||||
return 'unknown'
|
return None
|
||||||
|
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
# Git CLI is not installed or not in PATH
|
# Git CLI is not installed or not in PATH
|
||||||
return 'unknown'
|
return None
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
# Git command failed (e.g., not a git repo, no remote, etc.)
|
# Git command failed (e.g., not a git repo, no remote, etc.)
|
||||||
return 'unknown'
|
return None
|
||||||
except Exception:
|
except Exception:
|
||||||
# Any other unexpected error
|
# Any other unexpected error
|
||||||
return 'unknown'
|
return None
|
||||||
|
|
||||||
repo = get_github_repo()
|
# WLED version is managed by package.json; this is picked up in several places
|
||||||
env.Append(BUILD_FLAGS=[f'-DWLED_REPO=\\"{repo}\\"'])
|
# - 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"
|
||||||
|
)
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
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}"])
|
|
||||||
@@ -10,7 +10,25 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
# CI/release binaries
|
# CI/release binaries
|
||||||
default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover, usermods
|
default_envs = nodemcuv2
|
||||||
|
esp8266_2m
|
||||||
|
esp01_1m_full
|
||||||
|
nodemcuv2_160
|
||||||
|
esp8266_2m_160
|
||||||
|
esp01_1m_full_160
|
||||||
|
nodemcuv2_compat
|
||||||
|
esp8266_2m_compat
|
||||||
|
esp01_1m_full_compat
|
||||||
|
esp32dev
|
||||||
|
esp32dev_debug
|
||||||
|
esp32_eth
|
||||||
|
esp32_wrover
|
||||||
|
lolin_s2_mini
|
||||||
|
esp32c3dev
|
||||||
|
esp32s3dev_16MB_opi
|
||||||
|
esp32s3dev_8MB_opi
|
||||||
|
esp32s3_4M_qspi
|
||||||
|
usermods
|
||||||
|
|
||||||
src_dir = ./wled00
|
src_dir = ./wled00
|
||||||
data_dir = ./wled00/data
|
data_dir = ./wled00/data
|
||||||
@@ -110,8 +128,7 @@ ldscript_4m1m = eagle.flash.4m1m.ld
|
|||||||
|
|
||||||
[scripts_defaults]
|
[scripts_defaults]
|
||||||
extra_scripts =
|
extra_scripts =
|
||||||
pre:pio-scripts/set_version.py
|
pre:pio-scripts/set_metadata.py
|
||||||
pre:pio-scripts/set_repo.py
|
|
||||||
post:pio-scripts/output_bins.py
|
post:pio-scripts/output_bins.py
|
||||||
post:pio-scripts/strip-floats.py
|
post:pio-scripts/strip-floats.py
|
||||||
pre:pio-scripts/user_config_copy.py
|
pre:pio-scripts/user_config_copy.py
|
||||||
@@ -265,12 +282,14 @@ AR_lib_deps = ;; for pre-usermod-library platformio_override compatibility
|
|||||||
|
|
||||||
[esp32_idf_V4]
|
[esp32_idf_V4]
|
||||||
;; build environment for ESP32 using ESP-IDF 4.4.x / arduino-esp32 v2.0.5
|
;; 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.
|
;; 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.
|
;; 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)
|
;; 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 = 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 =
|
||||||
build_unflags = ${common.build_unflags}
|
build_unflags = ${common.build_unflags}
|
||||||
build_flags = -g
|
build_flags = -g
|
||||||
-Wshadow=compatible-local ;; emit warning in case a local variable "shadows" another local one
|
-Wshadow=compatible-local ;; emit warning in case a local variable "shadows" another local one
|
||||||
@@ -285,6 +304,7 @@ lib_deps =
|
|||||||
[esp32s2]
|
[esp32s2]
|
||||||
;; generic definitions for all ESP32-S2 boards
|
;; generic definitions for all ESP32-S2 boards
|
||||||
platform = ${esp32_idf_V4.platform}
|
platform = ${esp32_idf_V4.platform}
|
||||||
|
platform_packages = ${esp32_idf_V4.platform_packages}
|
||||||
build_unflags = ${common.build_unflags}
|
build_unflags = ${common.build_unflags}
|
||||||
build_flags = -g
|
build_flags = -g
|
||||||
-DARDUINO_ARCH_ESP32
|
-DARDUINO_ARCH_ESP32
|
||||||
@@ -303,6 +323,7 @@ board_build.partitions = ${esp32.default_partitions} ;; default partioning for
|
|||||||
[esp32c3]
|
[esp32c3]
|
||||||
;; generic definitions for all ESP32-C3 boards
|
;; generic definitions for all ESP32-C3 boards
|
||||||
platform = ${esp32_idf_V4.platform}
|
platform = ${esp32_idf_V4.platform}
|
||||||
|
platform_packages = ${esp32_idf_V4.platform_packages}
|
||||||
build_unflags = ${common.build_unflags}
|
build_unflags = ${common.build_unflags}
|
||||||
build_flags = -g
|
build_flags = -g
|
||||||
-DARDUINO_ARCH_ESP32
|
-DARDUINO_ARCH_ESP32
|
||||||
@@ -321,6 +342,7 @@ board_build.flash_mode = qio
|
|||||||
[esp32s3]
|
[esp32s3]
|
||||||
;; generic definitions for all ESP32-S3 boards
|
;; generic definitions for all ESP32-S3 boards
|
||||||
platform = ${esp32_idf_V4.platform}
|
platform = ${esp32_idf_V4.platform}
|
||||||
|
platform_packages = ${esp32_idf_V4.platform_packages}
|
||||||
build_unflags = ${common.build_unflags}
|
build_unflags = ${common.build_unflags}
|
||||||
build_flags = -g
|
build_flags = -g
|
||||||
-DESP32
|
-DESP32
|
||||||
@@ -429,21 +451,31 @@ custom_usermods = audioreactive
|
|||||||
[env:esp32dev]
|
[env:esp32dev]
|
||||||
board = esp32dev
|
board = esp32dev
|
||||||
platform = ${esp32_idf_V4.platform}
|
platform = ${esp32_idf_V4.platform}
|
||||||
|
platform_packages = ${esp32_idf_V4.platform_packages}
|
||||||
build_unflags = ${common.build_unflags}
|
build_unflags = ${common.build_unflags}
|
||||||
custom_usermods = audioreactive
|
custom_usermods = audioreactive
|
||||||
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_V4\" #-D WLED_DISABLE_BROWNOUT_DET
|
build_flags = ${common.build_flags} ${esp32_idf_V4.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
|
-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_V4.lib_deps}
|
||||||
monitor_filters = esp32_exception_decoder
|
monitor_filters = esp32_exception_decoder
|
||||||
board_build.partitions = ${esp32.default_partitions}
|
board_build.partitions = ${esp32.default_partitions}
|
||||||
board_build.flash_mode = dio
|
board_build.flash_mode = dio
|
||||||
|
|
||||||
|
[env:esp32dev_debug]
|
||||||
|
extends = env:esp32dev
|
||||||
|
upload_speed = 921600
|
||||||
|
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags}
|
||||||
|
-D WLED_DEBUG
|
||||||
|
-D WLED_RELEASE_NAME=\"ESP32_DEBUG\"
|
||||||
|
|
||||||
[env:esp32dev_8M]
|
[env:esp32dev_8M]
|
||||||
board = esp32dev
|
board = esp32dev
|
||||||
platform = ${esp32_idf_V4.platform}
|
platform = ${esp32_idf_V4.platform}
|
||||||
|
platform_packages = ${esp32_idf_V4.platform_packages}
|
||||||
custom_usermods = audioreactive
|
custom_usermods = audioreactive
|
||||||
build_unflags = ${common.build_unflags}
|
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
|
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}
|
lib_deps = ${esp32_idf_V4.lib_deps}
|
||||||
monitor_filters = esp32_exception_decoder
|
monitor_filters = esp32_exception_decoder
|
||||||
board_build.partitions = ${esp32.large_partitions}
|
board_build.partitions = ${esp32.large_partitions}
|
||||||
@@ -455,9 +487,11 @@ board_build.flash_mode = dio
|
|||||||
[env:esp32dev_16M]
|
[env:esp32dev_16M]
|
||||||
board = esp32dev
|
board = esp32dev
|
||||||
platform = ${esp32_idf_V4.platform}
|
platform = ${esp32_idf_V4.platform}
|
||||||
|
platform_packages = ${esp32_idf_V4.platform_packages}
|
||||||
custom_usermods = audioreactive
|
custom_usermods = audioreactive
|
||||||
build_unflags = ${common.build_unflags}
|
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
|
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}
|
lib_deps = ${esp32_idf_V4.lib_deps}
|
||||||
monitor_filters = esp32_exception_decoder
|
monitor_filters = esp32_exception_decoder
|
||||||
board_build.partitions = ${esp32.extreme_partitions}
|
board_build.partitions = ${esp32.extreme_partitions}
|
||||||
@@ -469,10 +503,12 @@ board_build.flash_mode = dio
|
|||||||
[env:esp32_eth]
|
[env:esp32_eth]
|
||||||
board = esp32-poe
|
board = esp32-poe
|
||||||
platform = ${esp32_idf_V4.platform}
|
platform = ${esp32_idf_V4.platform}
|
||||||
|
platform_packages = ${esp32_idf_V4.platform_packages}
|
||||||
upload_speed = 921600
|
upload_speed = 921600
|
||||||
custom_usermods = audioreactive
|
custom_usermods = audioreactive
|
||||||
build_unflags = ${common.build_unflags}
|
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
|
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
|
||||||
; -D WLED_DISABLE_ESPNOW ;; ESP-NOW requires wifi, may crash with ethernet only
|
; -D WLED_DISABLE_ESPNOW ;; ESP-NOW requires wifi, may crash with ethernet only
|
||||||
lib_deps = ${esp32.lib_deps}
|
lib_deps = ${esp32.lib_deps}
|
||||||
board_build.partitions = ${esp32.default_partitions}
|
board_build.partitions = ${esp32.default_partitions}
|
||||||
@@ -487,6 +523,7 @@ board_build.partitions = ${esp32.extended_partitions}
|
|||||||
custom_usermods = audioreactive
|
custom_usermods = audioreactive
|
||||||
build_unflags = ${common.build_unflags}
|
build_unflags = ${common.build_unflags}
|
||||||
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_WROVER\"
|
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
|
||||||
-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
|
-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
|
-D DATA_PINS=25
|
||||||
lib_deps = ${esp32_idf_V4.lib_deps}
|
lib_deps = ${esp32_idf_V4.lib_deps}
|
||||||
@@ -494,6 +531,7 @@ lib_deps = ${esp32_idf_V4.lib_deps}
|
|||||||
[env:esp32c3dev]
|
[env:esp32c3dev]
|
||||||
extends = esp32c3
|
extends = esp32c3
|
||||||
platform = ${esp32c3.platform}
|
platform = ${esp32c3.platform}
|
||||||
|
platform_packages = ${esp32c3.platform_packages}
|
||||||
framework = arduino
|
framework = arduino
|
||||||
board = esp32-c3-devkitm-1
|
board = esp32-c3-devkitm-1
|
||||||
board_build.partitions = ${esp32.default_partitions}
|
board_build.partitions = ${esp32.default_partitions}
|
||||||
@@ -511,6 +549,7 @@ lib_deps = ${esp32c3.lib_deps}
|
|||||||
board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support
|
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
|
board_build.arduino.memory_type = qio_opi ;; use with PSRAM: 8MB or 16MB
|
||||||
platform = ${esp32s3.platform}
|
platform = ${esp32s3.platform}
|
||||||
|
platform_packages = ${esp32s3.platform_packages}
|
||||||
upload_speed = 921600
|
upload_speed = 921600
|
||||||
custom_usermods = audioreactive
|
custom_usermods = audioreactive
|
||||||
build_unflags = ${common.build_unflags}
|
build_unflags = ${common.build_unflags}
|
||||||
@@ -532,6 +571,7 @@ monitor_filters = esp32_exception_decoder
|
|||||||
board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support
|
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
|
board_build.arduino.memory_type = qio_opi ;; use with PSRAM: 8MB or 16MB
|
||||||
platform = ${esp32s3.platform}
|
platform = ${esp32s3.platform}
|
||||||
|
platform_packages = ${esp32s3.platform_packages}
|
||||||
upload_speed = 921600
|
upload_speed = 921600
|
||||||
custom_usermods = audioreactive
|
custom_usermods = audioreactive
|
||||||
build_unflags = ${common.build_unflags}
|
build_unflags = ${common.build_unflags}
|
||||||
@@ -550,6 +590,7 @@ monitor_filters = esp32_exception_decoder
|
|||||||
;; For ESP32-S3 WROOM-2, a.k.a. ESP32-S3 DevKitC-1 v1.1
|
;; For ESP32-S3 WROOM-2, a.k.a. ESP32-S3 DevKitC-1 v1.1
|
||||||
;; with >= 16MB FLASH and >= 8MB PSRAM (memory_type: opi_opi)
|
;; with >= 16MB FLASH and >= 8MB PSRAM (memory_type: opi_opi)
|
||||||
platform = ${esp32s3.platform}
|
platform = ${esp32s3.platform}
|
||||||
|
platform_packages = ${esp32s3.platform_packages}
|
||||||
board = esp32s3camlcd ;; this is the only standard board with "opi_opi"
|
board = esp32s3camlcd ;; this is the only standard board with "opi_opi"
|
||||||
board_build.arduino.memory_type = opi_opi
|
board_build.arduino.memory_type = opi_opi
|
||||||
upload_speed = 921600
|
upload_speed = 921600
|
||||||
@@ -575,6 +616,7 @@ monitor_filters = esp32_exception_decoder
|
|||||||
;; ESP32-S3, with 4MB FLASH and <= 4MB PSRAM (memory_type: qio_qspi)
|
;; ESP32-S3, with 4MB FLASH and <= 4MB PSRAM (memory_type: qio_qspi)
|
||||||
board = lolin_s3_mini ;; -S3 mini, 4MB flash 2MB PSRAM
|
board = lolin_s3_mini ;; -S3 mini, 4MB flash 2MB PSRAM
|
||||||
platform = ${esp32s3.platform}
|
platform = ${esp32s3.platform}
|
||||||
|
platform_packages = ${esp32s3.platform_packages}
|
||||||
upload_speed = 921600
|
upload_speed = 921600
|
||||||
custom_usermods = audioreactive
|
custom_usermods = audioreactive
|
||||||
build_unflags = ${common.build_unflags}
|
build_unflags = ${common.build_unflags}
|
||||||
@@ -591,6 +633,7 @@ monitor_filters = esp32_exception_decoder
|
|||||||
|
|
||||||
[env:lolin_s2_mini]
|
[env:lolin_s2_mini]
|
||||||
platform = ${esp32s2.platform}
|
platform = ${esp32s2.platform}
|
||||||
|
platform_packages = ${esp32s2.platform_packages}
|
||||||
board = lolin_s2_mini
|
board = lolin_s2_mini
|
||||||
board_build.partitions = ${esp32.default_partitions}
|
board_build.partitions = ${esp32.default_partitions}
|
||||||
board_build.flash_mode = qio
|
board_build.flash_mode = qio
|
||||||
@@ -617,6 +660,7 @@ lib_deps = ${esp32s2.lib_deps}
|
|||||||
[env:usermods]
|
[env:usermods]
|
||||||
board = esp32dev
|
board = esp32dev
|
||||||
platform = ${esp32_idf_V4.platform}
|
platform = ${esp32_idf_V4.platform}
|
||||||
|
platform_packages = ${esp32_idf_V4.platform_packages}
|
||||||
build_unflags = ${common.build_unflags}
|
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_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_USERMODS\"
|
||||||
-DTOUCH_CS=9
|
-DTOUCH_CS=9
|
||||||
|
|||||||
@@ -541,12 +541,15 @@ build_flags = ${common.build_flags}
|
|||||||
-D WLED_ENABLE_HUB75MATRIX -D NO_GFX
|
-D WLED_ENABLE_HUB75MATRIX -D NO_GFX
|
||||||
-D WLED_DEBUG_BUS
|
-D WLED_DEBUG_BUS
|
||||||
; -D WLED_DEBUG
|
; -D WLED_DEBUG
|
||||||
|
-D SR_DMTYPE=-1 -D I2S_SDPIN=-1 -D I2S_CKPIN=-1 -D I2S_WSPIN=-1 -D MCLK_PIN=-1 ;; Disable to prevent pin clash
|
||||||
|
|
||||||
lib_deps = ${esp32_idf_V4.lib_deps}
|
lib_deps = ${esp32_idf_V4.lib_deps}
|
||||||
https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA.git#3.0.11
|
https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA.git#3.0.11
|
||||||
|
|
||||||
monitor_filters = esp32_exception_decoder
|
monitor_filters = esp32_exception_decoder
|
||||||
board_build.partitions = ${esp32.default_partitions}
|
board_build.partitions = ${esp32.default_partitions}
|
||||||
board_build.flash_mode = dio
|
board_build.flash_mode = dio
|
||||||
|
custom_usermods = audioreactive
|
||||||
|
|
||||||
[env:esp32dev_hub75_forum_pinout]
|
[env:esp32dev_hub75_forum_pinout]
|
||||||
extends = env:esp32dev_hub75
|
extends = env:esp32dev_hub75
|
||||||
@@ -555,10 +558,10 @@ build_flags = ${common.build_flags}
|
|||||||
-D WLED_ENABLE_HUB75MATRIX -D NO_GFX
|
-D WLED_ENABLE_HUB75MATRIX -D NO_GFX
|
||||||
-D ESP32_FORUM_PINOUT ;; enable for SmartMatrix default pins
|
-D ESP32_FORUM_PINOUT ;; enable for SmartMatrix default pins
|
||||||
-D WLED_DEBUG_BUS
|
-D WLED_DEBUG_BUS
|
||||||
|
-D SR_DMTYPE=-1 -D I2S_SDPIN=-1 -D I2S_CKPIN=-1 -D I2S_WSPIN=-1 -D MCLK_PIN=-1 ;; Disable to prevent pin clash
|
||||||
; -D WLED_DEBUG
|
; -D WLED_DEBUG
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[env:adafruit_matrixportal_esp32s3]
|
[env:adafruit_matrixportal_esp32s3]
|
||||||
; ESP32-S3 processor, 8 MB flash, 2 MB of PSRAM, dedicated driver pins for HUB75
|
; ESP32-S3 processor, 8 MB flash, 2 MB of PSRAM, dedicated driver pins for HUB75
|
||||||
board = adafruit_matrixportal_esp32s3
|
board = adafruit_matrixportal_esp32s3
|
||||||
@@ -575,6 +578,7 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=
|
|||||||
-D S3_LCD_DIV_NUM=20 ;; Attempt to fix wifi performance issue when panel active with S3 chips
|
-D S3_LCD_DIV_NUM=20 ;; Attempt to fix wifi performance issue when panel active with S3 chips
|
||||||
-D ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3
|
-D ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3
|
||||||
-D WLED_DEBUG_BUS
|
-D WLED_DEBUG_BUS
|
||||||
|
-D SR_DMTYPE=-1 -D I2S_SDPIN=-1 -D I2S_CKPIN=-1 -D I2S_WSPIN=-1 -D MCLK_PIN=-1 ;; Disable to prevent pin clash
|
||||||
|
|
||||||
|
|
||||||
lib_deps = ${esp32s3.lib_deps}
|
lib_deps = ${esp32s3.lib_deps}
|
||||||
@@ -584,6 +588,7 @@ board_build.partitions = ${esp32.default_partitions}
|
|||||||
board_build.f_flash = 80000000L
|
board_build.f_flash = 80000000L
|
||||||
board_build.flash_mode = qio
|
board_build.flash_mode = qio
|
||||||
monitor_filters = esp32_exception_decoder
|
monitor_filters = esp32_exception_decoder
|
||||||
|
custom_usermods = audioreactive
|
||||||
|
|
||||||
[env:esp32S3_PSRAM_HUB75]
|
[env:esp32S3_PSRAM_HUB75]
|
||||||
;; MOONHUB HUB75 adapter board
|
;; MOONHUB HUB75 adapter board
|
||||||
@@ -601,6 +606,8 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=
|
|||||||
-D S3_LCD_DIV_NUM=20 ;; Attempt to fix wifi performance issue when panel active with S3 chips
|
-D S3_LCD_DIV_NUM=20 ;; Attempt to fix wifi performance issue when panel active with S3 chips
|
||||||
-D MOONHUB_S3_PINOUT ;; HUB75 pinout
|
-D MOONHUB_S3_PINOUT ;; HUB75 pinout
|
||||||
-D WLED_DEBUG_BUS
|
-D WLED_DEBUG_BUS
|
||||||
|
-D LEDPIN=14 -D BTNPIN=0 -D RLYPIN=15 -D IRPIN=-1 -D AUDIOPIN=-1 ;; defaults that avoid pin conflicts with HUB75
|
||||||
|
-D SR_DMTYPE=1 -D I2S_SDPIN=10 -D I2S_CKPIN=11 -D I2S_WSPIN=12 -D MCLK_PIN=-1 ;; I2S mic
|
||||||
|
|
||||||
lib_deps = ${esp32s3.lib_deps}
|
lib_deps = ${esp32s3.lib_deps}
|
||||||
https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA.git#aa28e2a ;; S3_LCD_DIV_NUM fix
|
https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA.git#aa28e2a ;; S3_LCD_DIV_NUM fix
|
||||||
@@ -609,3 +616,4 @@ board_build.partitions = ${esp32.default_partitions}
|
|||||||
board_build.f_flash = 80000000L
|
board_build.f_flash = 80000000L
|
||||||
board_build.flash_mode = qio
|
board_build.flash_mode = qio
|
||||||
monitor_filters = esp32_exception_decoder
|
monitor_filters = esp32_exception_decoder
|
||||||
|
custom_usermods = audioreactive
|
||||||
@@ -26,7 +26,7 @@ const packageJson = require("../package.json");
|
|||||||
// Export functions for testing
|
// Export functions for testing
|
||||||
module.exports = { isFileNewerThan, isAnyFileInFolderNewerThan };
|
module.exports = { isFileNewerThan, isAnyFileInFolderNewerThan };
|
||||||
|
|
||||||
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"]
|
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"]
|
||||||
|
|
||||||
// \x1b[34m is blue, \x1b[36m is cyan, \x1b[0m is reset
|
// \x1b[34m is blue, \x1b[36m is cyan, \x1b[0m is reset
|
||||||
const wledBanner = `
|
const wledBanner = `
|
||||||
@@ -246,6 +246,21 @@ writeHtmlGzipped("wled00/data/index.htm", "wled00/html_ui.h", 'index');
|
|||||||
writeHtmlGzipped("wled00/data/pixart/pixart.htm", "wled00/html_pixart.h", 'pixart');
|
writeHtmlGzipped("wled00/data/pixart/pixart.htm", "wled00/html_pixart.h", 'pixart');
|
||||||
//writeHtmlGzipped("wled00/data/cpal/cpal.htm", "wled00/html_cpal.h", 'cpal');
|
//writeHtmlGzipped("wled00/data/cpal/cpal.htm", "wled00/html_cpal.h", 'cpal');
|
||||||
writeHtmlGzipped("wled00/data/pxmagic/pxmagic.htm", "wled00/html_pxmagic.h", 'pxmagic');
|
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(
|
writeChunks(
|
||||||
"wled00/data/cpal",
|
"wled00/data/cpal",
|
||||||
@@ -388,12 +403,6 @@ const char PAGE_dmxmap[] PROGMEM = R"=====()=====";
|
|||||||
name: "PAGE_update",
|
name: "PAGE_update",
|
||||||
method: "gzip",
|
method: "gzip",
|
||||||
filter: "html-minify",
|
filter: "html-minify",
|
||||||
mangle: (str) =>
|
|
||||||
str
|
|
||||||
.replace(
|
|
||||||
/function GetV().*\<\/script\>/gms,
|
|
||||||
"</script><script src=\"/settings/s.js?p=9\"></script>"
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
file: "welcome.htm",
|
file: "welcome.htm",
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
// note: matrix may be comprised of multiple panels each with different orientation
|
// note: matrix may be comprised of multiple panels each with different orientation
|
||||||
// but ledmap takes care of that. ledmap is constructed upon initialization
|
// but ledmap takes care of that. ledmap is constructed upon initialization
|
||||||
// so matrix should disable regular ledmap processing
|
// 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() {
|
void WS2812FX::setUpMatrix() {
|
||||||
#ifndef WLED_DISABLE_2D
|
#ifndef WLED_DISABLE_2D
|
||||||
// isMatrix is set in cfg.cpp or set.cpp
|
// isMatrix is set in cfg.cpp or set.cpp
|
||||||
@@ -45,12 +46,12 @@ void WS2812FX::setUpMatrix() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend();
|
|
||||||
waitForIt();
|
|
||||||
|
|
||||||
customMappingSize = 0; // prevent use of mapping if anything goes wrong
|
customMappingSize = 0; // prevent use of mapping if anything goes wrong
|
||||||
|
|
||||||
d_free(customMappingTable);
|
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
|
customMappingTable = static_cast<uint16_t*>(d_malloc(sizeof(uint16_t)*getLengthTotal())); // prefer to not use SPI RAM
|
||||||
|
|
||||||
if (customMappingTable) {
|
if (customMappingTable) {
|
||||||
@@ -113,7 +114,6 @@ void WS2812FX::setUpMatrix() {
|
|||||||
|
|
||||||
// delete gap array as we no longer need it
|
// delete gap array as we no longer need it
|
||||||
p_free(gapTable);
|
p_free(gapTable);
|
||||||
resume();
|
|
||||||
|
|
||||||
#ifdef WLED_DEBUG
|
#ifdef WLED_DEBUG
|
||||||
DEBUG_PRINT(F("Matrix ledmap:"));
|
DEBUG_PRINT(F("Matrix ledmap:"));
|
||||||
|
|||||||
@@ -1681,9 +1681,13 @@ void WS2812FX::setTransitionMode(bool t) {
|
|||||||
resume();
|
resume();
|
||||||
}
|
}
|
||||||
|
|
||||||
// wait until frame is over (service() has finished or time for 1 frame has passed; yield() crashes on 8266)
|
// 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
|
||||||
void WS2812FX::waitForIt() {
|
void WS2812FX::waitForIt() {
|
||||||
unsigned long maxWait = millis() + getFrameTime() + 100; // TODO: this needs a proper fix for timeout!
|
unsigned long maxWait = millis() + 2*getFrameTime() + 100; // TODO: this needs a proper fix for timeout! see #4779
|
||||||
while (isServicing() && maxWait > millis()) delay(1);
|
while (isServicing() && maxWait > millis()) delay(1);
|
||||||
#ifdef WLED_DEBUG
|
#ifdef WLED_DEBUG
|
||||||
if (millis() >= maxWait) DEBUG_PRINTLN(F("Waited for strip to finish servicing."));
|
if (millis() >= maxWait) DEBUG_PRINTLN(F("Waited for strip to finish servicing."));
|
||||||
@@ -1810,7 +1814,11 @@ Segment& WS2812FX::getSegment(unsigned id) {
|
|||||||
return _segments[id >= _segments.size() ? getMainSegmentId() : id]; // vectors
|
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() {
|
void WS2812FX::resetSegments() {
|
||||||
|
if (isServicing()) return;
|
||||||
_segments.clear(); // destructs all Segment as part of clearing
|
_segments.clear(); // destructs all Segment as part of clearing
|
||||||
_segments.emplace_back(0, isMatrix ? Segment::maxWidth : _length, 0, isMatrix ? Segment::maxHeight : 1);
|
_segments.emplace_back(0, isMatrix ? Segment::maxWidth : _length, 0, isMatrix ? Segment::maxHeight : 1);
|
||||||
_segments.shrink_to_fit(); // just in case ...
|
_segments.shrink_to_fit(); // just in case ...
|
||||||
@@ -1818,6 +1826,7 @@ void WS2812FX::resetSegments() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WS2812FX::makeAutoSegments(bool forceReset) {
|
void WS2812FX::makeAutoSegments(bool forceReset) {
|
||||||
|
if (isServicing()) return;
|
||||||
if (autoSegments) { //make one segment per bus
|
if (autoSegments) { //make one segment per bus
|
||||||
unsigned segStarts[MAX_NUM_SEGMENTS] = {0};
|
unsigned segStarts[MAX_NUM_SEGMENTS] = {0};
|
||||||
unsigned segStops [MAX_NUM_SEGMENTS] = {0};
|
unsigned segStops [MAX_NUM_SEGMENTS] = {0};
|
||||||
@@ -1889,6 +1898,7 @@ void WS2812FX::makeAutoSegments(bool forceReset) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WS2812FX::fixInvalidSegments() {
|
void WS2812FX::fixInvalidSegments() {
|
||||||
|
if (isServicing()) return;
|
||||||
//make sure no segment is longer than total (sanity check)
|
//make sure no segment is longer than total (sanity check)
|
||||||
for (size_t i = getSegmentsNum()-1; i > 0; i--) {
|
for (size_t i = getSegmentsNum()-1; i > 0; i--) {
|
||||||
if (isMatrix) {
|
if (isMatrix) {
|
||||||
@@ -1951,6 +1961,7 @@ void WS2812FX::printSize() {
|
|||||||
|
|
||||||
// load custom mapping table from JSON file (called from finalizeInit() or deserializeState())
|
// 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
|
// 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) {
|
bool WS2812FX::deserializeMap(unsigned n) {
|
||||||
char fileName[32];
|
char fileName[32];
|
||||||
strcpy_P(fileName, PSTR("/ledmap"));
|
strcpy_P(fileName, PSTR("/ledmap"));
|
||||||
@@ -1980,9 +1991,6 @@ bool WS2812FX::deserializeMap(unsigned n) {
|
|||||||
} else
|
} else
|
||||||
DEBUG_PRINTF_P(PSTR("Reading LED map from %s\n"), fileName);
|
DEBUG_PRINTF_P(PSTR("Reading LED map from %s\n"), fileName);
|
||||||
|
|
||||||
suspend();
|
|
||||||
waitForIt();
|
|
||||||
|
|
||||||
JsonObject root = pDoc->as<JsonObject>();
|
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 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())) {
|
if (n == 0 && (!root[F("width")].isNull() || !root[F("height")].isNull())) {
|
||||||
@@ -2040,8 +2048,6 @@ bool WS2812FX::deserializeMap(unsigned n) {
|
|||||||
DEBUG_PRINTLN(F("ERROR LED map allocation error."));
|
DEBUG_PRINTLN(F("ERROR LED map allocation error."));
|
||||||
}
|
}
|
||||||
|
|
||||||
resume();
|
|
||||||
|
|
||||||
releaseJSONBufferLock();
|
releaseJSONBufferLock();
|
||||||
return (customMappingSize > 0);
|
return (customMappingSize > 0);
|
||||||
}
|
}
|
||||||
|
|||||||
1051
wled00/data/edit.htm
1051
wled00/data/edit.htm
File diff suppressed because it is too large
Load Diff
@@ -672,7 +672,6 @@ function parseInfo(i) {
|
|||||||
//syncTglRecv = i.str;
|
//syncTglRecv = i.str;
|
||||||
maxSeg = i.leds.maxseg;
|
maxSeg = i.leds.maxseg;
|
||||||
pmt = i.fs.pmt;
|
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";
|
gId('buttonNodes').style.display = lastinfo.ndc > 0 ? null:"none";
|
||||||
// do we have a matrix set-up
|
// do we have a matrix set-up
|
||||||
mw = i.leds.matrix ? i.leds.matrix.w : 0;
|
mw = i.leds.matrix ? i.leds.matrix.w : 0;
|
||||||
@@ -3151,7 +3150,6 @@ function togglePcMode(fromB = false)
|
|||||||
if (!fromB && ((wW < 1024 && lastw < 1024) || (wW >= 1024 && lastw >= 1024))) return; // no change in size and called from size()
|
if (!fromB && ((wW < 1024 && lastw < 1024) || (wW >= 1024 && lastw >= 1024))) return; // no change in size and called from size()
|
||||||
if (pcMode) openTab(0, true);
|
if (pcMode) openTab(0, true);
|
||||||
gId('buttonPcm').className = (pcMode) ? "active":"";
|
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";
|
gId('bot').style.height = (pcMode && !cfg.comp.pcmbot) ? "0":"auto";
|
||||||
sCol('--bh', gId('bot').clientHeight + "px");
|
sCol('--bh', gId('bot').clientHeight + "px");
|
||||||
_C.style.width = (pcMode || simplifiedUI)?'100%':'400%';
|
_C.style.width = (pcMode || simplifiedUI)?'100%':'400%';
|
||||||
|
|||||||
@@ -17,26 +17,65 @@
|
|||||||
}
|
}
|
||||||
window.open(getURL("/update?revert"),"_self");
|
window.open(getURL("/update?revert"),"_self");
|
||||||
}
|
}
|
||||||
function GetV() {/*injected values here*/}
|
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';
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
@import url("style.css");
|
@import url("style.css");
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
<body onload="GetV();">
|
||||||
<body onload="GetV()">
|
|
||||||
<h2>WLED Software Update</h2>
|
<h2>WLED Software Update</h2>
|
||||||
<form method='POST' action='./update' id='upd' enctype='multipart/form-data' onsubmit="toggle('upd')">
|
<form method='POST' action='./update' id='upd' enctype='multipart/form-data' onsubmit="toggle('upd')">
|
||||||
Installed version: <span class="sip">WLED ##VERSION##</span><br>
|
Installed version: <span class="sip installed-version">Loading...</span><br>
|
||||||
|
Release: <span class="sip release-name">Loading...</span><br>
|
||||||
Download the latest binary: <a href="https://github.com/wled-dev/WLED/releases" target="_blank"
|
Download the latest binary: <a href="https://github.com/wled-dev/WLED/releases" target="_blank"
|
||||||
style="vertical-align: text-bottom; display: inline-flex;">
|
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>
|
<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='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>
|
<button type="submit">Update!</button><br>
|
||||||
<hr class="sml">
|
<hr class="sml">
|
||||||
<button id="rev" type="button" onclick="cR()">Revert update</button><br>
|
<button id="rev" type="button" onclick="cR()">Revert update</button><br>
|
||||||
<button type="button" onclick="B()">Back</button>
|
<button type="button" onclick="B()">Back</button>
|
||||||
</form>
|
</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>
|
<div id="Noupd" class="hide"><b>Updating...</b><br>Please do not close or refresh the page :)</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -55,8 +55,8 @@ static dmx_config_t createConfig()
|
|||||||
config.software_version_id = VERSION;
|
config.software_version_id = VERSION;
|
||||||
strcpy(config.device_label, "WLED_MM");
|
strcpy(config.device_label, "WLED_MM");
|
||||||
|
|
||||||
const std::string versionString = "WLED_V" + std::to_string(VERSION);
|
const std::string dmxWledVersionString = "WLED_V" + std::to_string(VERSION);
|
||||||
strncpy(config.software_version_label, versionString.c_str(), 32);
|
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.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].description = "SINGLE_RGB";
|
||||||
|
|||||||
@@ -422,7 +422,7 @@ void prepareArtnetPollReply(ArtPollReply *reply) {
|
|||||||
|
|
||||||
reply->reply_port = ARTNET_DEFAULT_PORT;
|
reply->reply_port = ARTNET_DEFAULT_PORT;
|
||||||
|
|
||||||
char * numberEnd = versionString;
|
char * numberEnd = (char*) versionString; // strtol promises not to try to edit this.
|
||||||
reply->reply_version_h = (uint8_t)strtol(numberEnd, &numberEnd, 10);
|
reply->reply_version_h = (uint8_t)strtol(numberEnd, &numberEnd, 10);
|
||||||
numberEnd++;
|
numberEnd++;
|
||||||
reply->reply_version_l = (uint8_t)strtol(numberEnd, &numberEnd, 10);
|
reply->reply_version_l = (uint8_t)strtol(numberEnd, &numberEnd, 10);
|
||||||
|
|||||||
@@ -541,7 +541,6 @@ void handleSerial();
|
|||||||
void updateBaudRate(uint32_t rate);
|
void updateBaudRate(uint32_t rate);
|
||||||
|
|
||||||
//wled_server.cpp
|
//wled_server.cpp
|
||||||
void createEditHandler(bool enable);
|
|
||||||
void initServer();
|
void initServer();
|
||||||
void serveMessage(AsyncWebServerRequest* request, uint16_t code, const String& headl, const String& subl="", byte optionT=255);
|
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);
|
void serveJsonError(AsyncWebServerRequest* request, uint16_t code, uint16_t error);
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ namespace {
|
|||||||
if (a.custom1 != b.custom1) d |= SEG_DIFFERS_FX;
|
if (a.custom1 != b.custom1) d |= SEG_DIFFERS_FX;
|
||||||
if (a.custom2 != b.custom2) d |= SEG_DIFFERS_FX;
|
if (a.custom2 != b.custom2) d |= SEG_DIFFERS_FX;
|
||||||
if (a.custom3 != b.custom3) 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.startY != b.startY) d |= SEG_DIFFERS_BOUNDS;
|
||||||
if (a.stopY != b.stopY) d |= SEG_DIFFERS_BOUNDS;
|
if (a.stopY != b.stopY) d |= SEG_DIFFERS_BOUNDS;
|
||||||
|
|
||||||
@@ -817,6 +820,9 @@ void serializeInfo(JsonObject root)
|
|||||||
root[F("resetReason1")] = (int)rtc_get_reset_reason(1);
|
root[F("resetReason1")] = (int)rtc_get_reset_reason(1);
|
||||||
#endif
|
#endif
|
||||||
root[F("lwip")] = 0; //deprecated
|
root[F("lwip")] = 0; //deprecated
|
||||||
|
#ifndef WLED_DISABLE_OTA
|
||||||
|
root[F("bootloaderSHA256")] = getBootloaderSHA256Hex();
|
||||||
|
#endif
|
||||||
#else
|
#else
|
||||||
root[F("arch")] = "esp8266";
|
root[F("arch")] = "esp8266";
|
||||||
root[F("core")] = ESP.getCoreVersion();
|
root[F("core")] = ESP.getCoreVersion();
|
||||||
|
|||||||
728
wled00/ota_update.cpp
Normal file
728
wled00/ota_update.cpp
Normal file
@@ -0,0 +1,728 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#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
|
||||||
114
wled00/ota_update.h
Normal file
114
wled00/ota_update.h
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
// 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);
|
||||||
|
|
||||||
|
#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
|
||||||
|
|
||||||
@@ -613,7 +613,6 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
|
|||||||
#ifndef WLED_DISABLE_OTA
|
#ifndef WLED_DISABLE_OTA
|
||||||
aOtaEnabled = request->hasArg(F("AO"));
|
aOtaEnabled = request->hasArg(F("AO"));
|
||||||
#endif
|
#endif
|
||||||
//createEditHandler(correctPIN && !otaLock);
|
|
||||||
otaSameSubnet = request->hasArg(F("SU"));
|
otaSameSubnet = request->hasArg(F("SU"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -815,8 +814,13 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
strip.panel.shrink_to_fit(); // release unused memory
|
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.deserializeMap(); // (re)load default ledmap (will also setUpMatrix() if ledmap does not exist)
|
||||||
strip.makeAutoSegments(true); // force re-creation of segments
|
strip.makeAutoSegments(true); // force re-creation of segments
|
||||||
|
strip.resume();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -369,7 +369,6 @@ void checkSettingsPIN(const char* pin) {
|
|||||||
if (!correctPIN && millis() - lastEditTime < PIN_RETRY_COOLDOWN) return; // guard against PIN brute force
|
if (!correctPIN && millis() - lastEditTime < PIN_RETRY_COOLDOWN) return; // guard against PIN brute force
|
||||||
bool correctBefore = correctPIN;
|
bool correctBefore = correctPIN;
|
||||||
correctPIN = (strlen(settingsPIN) == 0 || strncmp(settingsPIN, pin, 4) == 0);
|
correctPIN = (strlen(settingsPIN) == 0 || strncmp(settingsPIN, pin, 4) == 0);
|
||||||
if (correctBefore != correctPIN) createEditHandler(correctPIN);
|
|
||||||
lastEditTime = millis();
|
lastEditTime = millis();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#define WLED_DEFINE_GLOBAL_VARS //only in one source file, wled.cpp!
|
#define WLED_DEFINE_GLOBAL_VARS //only in one source file, wled.cpp!
|
||||||
#include "wled.h"
|
#include "wled.h"
|
||||||
#include "wled_ethernet.h"
|
#include "wled_ethernet.h"
|
||||||
|
#include "ota_update.h"
|
||||||
#ifdef WLED_ENABLE_AOTA
|
#ifdef WLED_ENABLE_AOTA
|
||||||
#define NO_OTA_PORT
|
#define NO_OTA_PORT
|
||||||
#include <ArduinoOTA.h>
|
#include <ArduinoOTA.h>
|
||||||
@@ -166,7 +167,6 @@ void WLED::loop()
|
|||||||
// 15min PIN time-out
|
// 15min PIN time-out
|
||||||
if (strlen(settingsPIN)>0 && correctPIN && millis() - lastEditTime > PIN_TIMEOUT) {
|
if (strlen(settingsPIN)>0 && correctPIN && millis() - lastEditTime > PIN_TIMEOUT) {
|
||||||
correctPIN = false;
|
correctPIN = false;
|
||||||
createEditHandler(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// reconnect WiFi to clear stale allocations if heap gets too low
|
// reconnect WiFi to clear stale allocations if heap gets too low
|
||||||
@@ -174,8 +174,8 @@ void WLED::loop()
|
|||||||
uint32_t heap = getFreeHeapSize();
|
uint32_t heap = getFreeHeapSize();
|
||||||
if (heap < MIN_HEAP_SIZE && lastHeap < MIN_HEAP_SIZE) {
|
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
|
strip.resetSegments(); // remove all but one segments from memory
|
||||||
|
if (!Update.isRunning()) forceReconnect = true;
|
||||||
} else if (heap < MIN_HEAP_SIZE) {
|
} else if (heap < MIN_HEAP_SIZE) {
|
||||||
DEBUG_PRINTLN(F("Heap low, purging segments."));
|
DEBUG_PRINTLN(F("Heap low, purging segments."));
|
||||||
strip.purgeSegments();
|
strip.purgeSegments();
|
||||||
|
|||||||
@@ -189,11 +189,15 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument<PSRAM_Allocator>;
|
|||||||
#include "FastLED.h"
|
#include "FastLED.h"
|
||||||
#include "const.h"
|
#include "const.h"
|
||||||
#include "fcn_declare.h"
|
#include "fcn_declare.h"
|
||||||
|
#ifndef WLED_DISABLE_OTA
|
||||||
|
#include "ota_update.h"
|
||||||
|
#endif
|
||||||
#include "NodeStruct.h"
|
#include "NodeStruct.h"
|
||||||
#include "pin_manager.h"
|
#include "pin_manager.h"
|
||||||
#include "colors.h"
|
#include "colors.h"
|
||||||
#include "bus_manager.h"
|
#include "bus_manager.h"
|
||||||
#include "FX.h"
|
#include "FX.h"
|
||||||
|
#include "wled_metadata.h"
|
||||||
|
|
||||||
#ifndef CLIENT_SSID
|
#ifndef CLIENT_SSID
|
||||||
#define CLIENT_SSID DEFAULT_CLIENT_SSID
|
#define CLIENT_SSID DEFAULT_CLIENT_SSID
|
||||||
@@ -270,20 +274,6 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument<PSRAM_Allocator>;
|
|||||||
#define STRINGIFY(X) #X
|
#define STRINGIFY(X) #X
|
||||||
#define TOSTRING(X) STRINGIFY(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"
|
#define WLED_CODENAME "Niji"
|
||||||
|
|
||||||
// AP and OTA default passwords (for maximum security change them!)
|
// AP and OTA default passwords (for maximum security change them!)
|
||||||
|
|||||||
164
wled00/wled_metadata.cpp
Normal file
164
wled00/wled_metadata.cpp
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
61
wled00/wled_metadata.h
Normal file
61
wled00/wled_metadata.h
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
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);
|
||||||
@@ -1,11 +1,7 @@
|
|||||||
#include "wled.h"
|
#include "wled.h"
|
||||||
|
|
||||||
#ifndef WLED_DISABLE_OTA
|
#ifndef WLED_DISABLE_OTA
|
||||||
#ifdef ESP8266
|
#include "ota_update.h"
|
||||||
#include <Updater.h>
|
|
||||||
#else
|
|
||||||
#include <Update.h>
|
|
||||||
#endif
|
|
||||||
#endif
|
#endif
|
||||||
#include "html_ui.h"
|
#include "html_ui.h"
|
||||||
#include "html_settings.h"
|
#include "html_settings.h"
|
||||||
@@ -17,6 +13,8 @@
|
|||||||
#include "html_pxmagic.h"
|
#include "html_pxmagic.h"
|
||||||
#endif
|
#endif
|
||||||
#include "html_cpal.h"
|
#include "html_cpal.h"
|
||||||
|
#include "html_edit.h"
|
||||||
|
|
||||||
|
|
||||||
// define flash strings once (saves flash memory)
|
// define flash strings once (saves flash memory)
|
||||||
static const char s_redirecting[] PROGMEM = "Redirecting...";
|
static const char s_redirecting[] PROGMEM = "Redirecting...";
|
||||||
@@ -26,8 +24,16 @@ 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_rebooting [] PROGMEM = "Rebooting now...";
|
||||||
static const char s_notimplemented[] PROGMEM = "Not implemented";
|
static const char s_notimplemented[] PROGMEM = "Not implemented";
|
||||||
static const char s_accessdenied[] PROGMEM = "Access Denied";
|
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";
|
static const char _common_js[] PROGMEM = "/common.js";
|
||||||
|
|
||||||
|
|
||||||
//Is this an IP?
|
//Is this an IP?
|
||||||
static bool isIp(const String &str) {
|
static bool isIp(const String &str) {
|
||||||
for (size_t i = 0; i < str.length(); i++) {
|
for (size_t i = 0; i < str.length(); i++) {
|
||||||
@@ -71,9 +77,9 @@ static void setStaticContentCacheHeaders(AsyncWebServerResponse *response, int c
|
|||||||
#ifndef WLED_DEBUG
|
#ifndef WLED_DEBUG
|
||||||
// this header name is misleading, "no-cache" will not disable cache,
|
// 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
|
// it just revalidates on every load using the "If-None-Match" header with the last ETag value
|
||||||
response->addHeader(F("Cache-Control"), F("no-cache"));
|
response->addHeader(FPSTR(s_cache_control), F("no-cache"));
|
||||||
#else
|
#else
|
||||||
response->addHeader(F("Cache-Control"), F("no-store,max-age=0")); // prevent caching if debug build
|
response->addHeader(FPSTR(s_cache_control), F("no-store,max-age=0")); // prevent caching if debug build
|
||||||
#endif
|
#endif
|
||||||
char etag[32];
|
char etag[32];
|
||||||
generateEtag(etag, eTagSuffix);
|
generateEtag(etag, eTagSuffix);
|
||||||
@@ -176,6 +182,7 @@ static String msgProcessor(const String& var)
|
|||||||
return String();
|
return String();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool isFinal) {
|
static void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool isFinal) {
|
||||||
if (!correctPIN) {
|
if (!correctPIN) {
|
||||||
if (isFinal) request->send(401, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_unlock_cfg));
|
if (isFinal) request->send(401, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_unlock_cfg));
|
||||||
@@ -198,7 +205,7 @@ static void handleUpload(AsyncWebServerRequest *request, const String& filename,
|
|||||||
request->_tempFile.close();
|
request->_tempFile.close();
|
||||||
if (filename.indexOf(F("cfg.json")) >= 0) { // check for filename with or without slash
|
if (filename.indexOf(F("cfg.json")) >= 0) { // check for filename with or without slash
|
||||||
doReboot = true;
|
doReboot = true;
|
||||||
request->send(200, FPSTR(CONTENT_TYPE_PLAIN), F("Configuration restore successful.\nRebooting..."));
|
request->send(200, FPSTR(CONTENT_TYPE_PLAIN), F("Config restore ok.\nRebooting..."));
|
||||||
} else {
|
} else {
|
||||||
if (filename.indexOf(F("palette")) >= 0 && filename.indexOf(F(".json")) >= 0) loadCustomPalettes();
|
if (filename.indexOf(F("palette")) >= 0 && filename.indexOf(F(".json")) >= 0) loadCustomPalettes();
|
||||||
request->send(200, FPSTR(CONTENT_TYPE_PLAIN), F("File Uploaded!"));
|
request->send(200, FPSTR(CONTENT_TYPE_PLAIN), F("File Uploaded!"));
|
||||||
@@ -207,25 +214,94 @@ static void handleUpload(AsyncWebServerRequest *request, const String& filename,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void createEditHandler(bool enable) {
|
static const char _edit_htm[] PROGMEM = "/edit.htm";
|
||||||
|
|
||||||
|
void createEditHandler() {
|
||||||
if (editHandler != nullptr) server.removeHandler(editHandler);
|
if (editHandler != nullptr) server.removeHandler(editHandler);
|
||||||
if (enable) {
|
|
||||||
#ifdef WLED_ENABLE_FS_EDITOR
|
editHandler = &server.on(F("/edit"), static_cast<WebRequestMethod>(HTTP_GET), [](AsyncWebServerRequest *request) {
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
// PIN check for GET/DELETE, for POST it is done in handleUpload()
|
||||||
editHandler = &server.addHandler(new SPIFFSEditor(WLED_FS));//http_username,http_password));
|
if (!correctPIN) {
|
||||||
#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);
|
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)
|
static bool captivePortal(AsyncWebServerRequest *request)
|
||||||
@@ -391,7 +467,7 @@ void initServer()
|
|||||||
size_t len, bool isFinal) {handleUpload(request, filename, index, data, len, isFinal);}
|
size_t len, bool isFinal) {handleUpload(request, filename, index, data, len, isFinal);}
|
||||||
);
|
);
|
||||||
|
|
||||||
createEditHandler(correctPIN);
|
createEditHandler(); // initialize "/edit" handler, access is protected by "correctPIN"
|
||||||
|
|
||||||
static const char _update[] PROGMEM = "/update";
|
static const char _update[] PROGMEM = "/update";
|
||||||
#ifndef WLED_DISABLE_OTA
|
#ifndef WLED_DISABLE_OTA
|
||||||
@@ -404,59 +480,47 @@ void initServer()
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.on(_update, HTTP_POST, [](AsyncWebServerRequest *request){
|
server.on(_update, HTTP_POST, [](AsyncWebServerRequest *request){
|
||||||
if (!correctPIN) {
|
if (request->_tempObject) {
|
||||||
serveSettings(request, true); // handle PIN page POST request
|
auto ota_result = getOTAResult(request);
|
||||||
return;
|
if (ota_result.first) {
|
||||||
}
|
if (ota_result.second.length() > 0) {
|
||||||
if (otaLock) {
|
serveMessage(request, 500, F("Update failed!"), ota_result.second, 254);
|
||||||
serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_ota), 254);
|
} else {
|
||||||
return;
|
serveMessage(request, 200, F("Update successful!"), FPSTR(s_rebooting), 131);
|
||||||
}
|
}
|
||||||
if (Update.hasError()) {
|
}
|
||||||
serveMessage(request, 500, F("Update failed!"), F("Please check your file and retry!"), 254);
|
|
||||||
} else {
|
} else {
|
||||||
serveMessage(request, 200, F("Update successful!"), FPSTR(s_rebooting), 131);
|
// No context structure - something's gone horribly wrong
|
||||||
#ifndef ESP8266
|
serveMessage(request, 500, F("Update failed!"), F("Internal server fault"), 254);
|
||||||
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){
|
},[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool isFinal){
|
||||||
IPAddress client = request->client()->remoteIP();
|
if (index == 0) {
|
||||||
if (((otaSameSubnet && !inSameSubnet(client)) && !strlen(settingsPIN)) || (!otaSameSubnet && !inLocalSubnet(client))) {
|
// Allocate the context structure
|
||||||
DEBUG_PRINTLN(F("Attempted OTA update from different/non-local subnet!"));
|
if (!initOTA(request)) {
|
||||||
request->send(401, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_accessdenied));
|
return; // Error will be dealt with after upload in response handler, above
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
if (!correctPIN || otaLock) return;
|
// Privilege checks
|
||||||
if(!index){
|
IPAddress client = request->client()->remoteIP();
|
||||||
DEBUG_PRINTLN(F("OTA Update Start"));
|
if (((otaSameSubnet && !inSameSubnet(client)) && !strlen(settingsPIN)) || (!otaSameSubnet && !inLocalSubnet(client))) {
|
||||||
#if WLED_WATCHDOG_TIMEOUT > 0
|
DEBUG_PRINTLN(F("Attempted OTA update from different/non-local subnet!"));
|
||||||
WLED::instance().disableWatchdog();
|
serveMessage(request, 401, FPSTR(s_accessdenied), F("Client is not on local subnet."), 254);
|
||||||
#endif
|
setOTAReplied(request);
|
||||||
UsermodManager::onUpdateBegin(true); // notify usermods that update is about to begin (some may require task de-init)
|
return;
|
||||||
lastEditTime = millis(); // make sure PIN does not lock during update
|
}
|
||||||
strip.suspend();
|
if (!correctPIN) {
|
||||||
backupConfig(); // backup current config in case the update ends badly
|
serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_cfg), 254);
|
||||||
strip.resetSegments(); // free as much memory as you can
|
setOTAReplied(request);
|
||||||
#ifdef ESP8266
|
return;
|
||||||
Update.runAsync(true);
|
};
|
||||||
#endif
|
if (otaLock) {
|
||||||
Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000);
|
serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_ota), 254);
|
||||||
}
|
setOTAReplied(request);
|
||||||
if(!Update.hasError()) Update.write(data, len);
|
return;
|
||||||
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
|
#else
|
||||||
const auto notSupported = [](AsyncWebServerRequest *request){
|
const auto notSupported = [](AsyncWebServerRequest *request){
|
||||||
@@ -466,6 +530,53 @@ void initServer()
|
|||||||
server.on(_update, HTTP_POST, notSupported, [](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool isFinal){});
|
server.on(_update, HTTP_POST, notSupported, [](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool isFinal){});
|
||||||
#endif
|
#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
|
#ifdef WLED_ENABLE_DMX
|
||||||
server.on(F("/dmxmap"), HTTP_GET, [](AsyncWebServerRequest *request){
|
server.on(F("/dmxmap"), HTTP_GET, [](AsyncWebServerRequest *request){
|
||||||
request->send_P(200, FPSTR(CONTENT_TYPE_HTML), PAGE_dmxmap, dmxProcessor);
|
request->send_P(200, FPSTR(CONTENT_TYPE_HTML), PAGE_dmxmap, dmxProcessor);
|
||||||
@@ -569,8 +680,8 @@ void serveSettingsJS(AsyncWebServerRequest* request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
AsyncResponseStream *response = request->beginResponseStream(FPSTR(CONTENT_TYPE_JAVASCRIPT));
|
AsyncResponseStream *response = request->beginResponseStream(FPSTR(CONTENT_TYPE_JAVASCRIPT));
|
||||||
response->addHeader(F("Cache-Control"), F("no-store"));
|
response->addHeader(FPSTR(s_cache_control), FPSTR(s_no_store));
|
||||||
response->addHeader(F("Expires"), F("0"));
|
response->addHeader(FPSTR(s_expires), F("0"));
|
||||||
|
|
||||||
response->print(F("function GetV(){var d=document;"));
|
response->print(F("function GetV(){var d=document;"));
|
||||||
getSettingsJS(subPage, *response);
|
getSettingsJS(subPage, *response);
|
||||||
@@ -694,7 +805,6 @@ void serveSettings(AsyncWebServerRequest* request, bool post) {
|
|||||||
#endif
|
#endif
|
||||||
case SUBPAGE_LOCK : {
|
case SUBPAGE_LOCK : {
|
||||||
correctPIN = !strlen(settingsPIN); // lock if a pin is set
|
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);
|
serveMessage(request, 200, strlen(settingsPIN) > 0 ? PSTR("Settings locked") : PSTR("No PIN set"), FPSTR(s_redirecting), 1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -671,16 +671,6 @@ void getSettingsJS(byte subPage, Print& settingsScript)
|
|||||||
UsermodManager::appendConfigData(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
|
if (subPage == SUBPAGE_2D) // 2D matrices
|
||||||
{
|
{
|
||||||
printSetFormValue(settingsScript,PSTR("SOMP"),strip.isMatrix);
|
printSetFormValue(settingsScript,PSTR("SOMP"),strip.isMatrix);
|
||||||
|
|||||||
Reference in New Issue
Block a user