Compare commits

...

20 Commits

Author SHA1 Message Date
Frank
730205ded5 AR: SR_DMTYPE=254 => UDP sound receive only (experimental)
additional dmtype = 254 "driver" that keeps AR enabled in "sound sync only" mode.
2025-11-23 00:24:49 +01:00
Frank
90ca6ccf8b AR: handle stupid build flag SR_DMTYPE=-1
I don't know how the bad example "-D SR_DMTYPE=-1" made it into platformio_override.sample.ini  🫣
mic type -1 = 255 was never supported by AR, and lead to undefined behavior due to a missing "case" in setup().

Fixed. Its still a stupid build_flags option, but at least now its handled properly.
2025-11-23 00:02:56 +01:00
Frank
5b3cc753e2 partition files for use with ADAFRUIT boards
these partition files preserve the special "UF2" bootloader that is necessary for adafruit -S2 and -S3 boards.
2025-11-22 18:06:02 +01:00
Will Tatam
4615eb8258 Merge pull request #5093 from netmindz/deviceId
Add Device ID to JSON Info
2025-11-22 12:17:09 +00:00
Will Tatam
9b787e13d1 swap to using ESP.getFlashChipId for the 8266 2025-11-21 08:22:22 +00:00
Will Tatam
3dbcd79b3c Add efuse based data to salt 2025-11-21 08:17:38 +00:00
Will Tatam
a1aac452de use correct value for deviceString for 8266 and add comments 2025-11-20 00:24:24 +00:00
Will Tatam
b90fbe6b1a fix whitespace 2025-11-19 23:53:21 +00:00
Will Tatam
1860258deb deviceString for esp32 2025-11-19 23:48:30 +00:00
Will Tatam
a2935b87c2 deviceString for 8266 2025-11-19 23:45:55 +00:00
Will Tatam
c1ce1d8aba salt using additional hardware details 2025-11-19 23:11:31 +00:00
Frank
54b7dfe04b Fix debug message for servicing wait
forgot to adjust the debug condition in my previous commit.

NB: the condition only shows a debug message when the max wait time was exceeded, which can only happen when line 1692 has waited for the maximum allowed time. ->Is this intended?
2025-11-18 23:05:03 +01:00
Frank
4a33809d66 make waitForIt() timing logic robust against millis() rollover
the timing logic did not work in case that millis()+100 + frametime rolls over; in this case millis() > maxWait, and waiting would be skipped which might lead to crashes.
-> logic slightly adjusted to be robust against rollover.
2025-11-18 22:56:30 +01:00
Damian Schneider
336e074b4a fix for 0byte size files, also made reading ledmaps more efficient
when a ledmap is read from a file, it first parses the keys, putting the in front is more efficient as it will find them in the first 256 byte chunk.
2025-11-18 20:40:04 +01:00
Damian Schneider
aaad450175 show minimum of 0.1KB for small files in file editor 2025-11-18 07:26:17 +01:00
Will Tatam
85b3c5d91b refactor to use a common sha1 function 2025-11-18 05:53:12 +00:00
Will Tatam
4db86ebf7f Add salf and checksum 2025-11-18 05:35:49 +00:00
Damian Schneider
65c43b5224 add ctrl+s support to file editor, also add toast instead of alert 2025-11-17 20:56:49 +01:00
Will Tatam
c649ec1d8c Update wled00/json.cpp
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-11-17 17:40:09 +00:00
Will Tatam
66ffd65476 Add deviceId to JSON info respose, to be used for the post-upgrade notfication system 2025-11-15 20:17:23 +00:00
9 changed files with 166 additions and 12 deletions

View File

@@ -0,0 +1,10 @@
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
# bootloader.bin,, 0x1000, 32K
# partition table,, 0x8000, 4K
nvs, data, nvs, 0x9000, 20K,
otadata, data, ota, 0xe000, 8K,
ota_0, app, ota_0, 0x10000, 2048K,
ota_1, app, ota_1, 0x210000, 2048K,
uf2, app, factory,0x410000, 256K,
spiffs, data, spiffs, 0x450000, 11968K,
1 # ESP-IDF Partition Table
2 # Name, Type, SubType, Offset, Size, Flags
3 # bootloader.bin,, 0x1000, 32K
4 # partition table,, 0x8000, 4K
5 nvs, data, nvs, 0x9000, 20K,
6 otadata, data, ota, 0xe000, 8K,
7 ota_0, app, ota_0, 0x10000, 2048K,
8 ota_1, app, ota_1, 0x210000, 2048K,
9 uf2, app, factory,0x410000, 256K,
10 spiffs, data, spiffs, 0x450000, 11968K,

View File

@@ -0,0 +1,11 @@
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
# bootloader.bin,, 0x1000, 32K
# partition table, 0x8000, 4K
nvs, data, nvs, 0x9000, 20K,
otadata, data, ota, 0xe000, 8K,
ota_0, 0, ota_0, 0x10000, 1408K,
ota_1, 0, ota_1, 0x170000, 1408K,
uf2, app, factory,0x2d0000, 256K,
spiffs, data, spiffs, 0x310000, 960K,
1 # ESP-IDF Partition Table
2 # Name, Type, SubType, Offset, Size, Flags
3 # bootloader.bin,, 0x1000, 32K
4 # partition table, 0x8000, 4K
5 nvs, data, nvs, 0x9000, 20K,
6 otadata, data, ota, 0xe000, 8K,
7 ota_0, 0, ota_0, 0x10000, 1408K,
8 ota_1, 0, ota_1, 0x170000, 1408K,
9 uf2, app, factory,0x2d0000, 256K,
10 spiffs, data, spiffs, 0x310000, 960K,

View File

@@ -0,0 +1,10 @@
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
# bootloader.bin,, 0x1000, 32K
# partition table,, 0x8000, 4K
nvs, data, nvs, 0x9000, 20K,
otadata, data, ota, 0xe000, 8K,
ota_0, app, ota_0, 0x10000, 2048K,
ota_1, app, ota_1, 0x210000, 2048K,
uf2, app, factory,0x410000, 256K,
spiffs, data, spiffs, 0x450000, 3776K,
1 # ESP-IDF Partition Table
2 # Name, Type, SubType, Offset, Size, Flags
3 # bootloader.bin,, 0x1000, 32K
4 # partition table,, 0x8000, 4K
5 nvs, data, nvs, 0x9000, 20K,
6 otadata, data, ota, 0xe000, 8K,
7 ota_0, app, ota_0, 0x10000, 2048K,
8 ota_1, app, ota_1, 0x210000, 2048K,
9 uf2, app, factory,0x410000, 256K,
10 spiffs, data, spiffs, 0x450000, 3776K,

View File

@@ -1227,7 +1227,6 @@ class AudioReactive : public Usermod {
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
// ADC over I2S is only possible on "classic" ESP32 // ADC over I2S is only possible on "classic" ESP32
case 0: case 0:
default:
DEBUGSR_PRINTLN(F("AR: Analog Microphone (left channel only).")); DEBUGSR_PRINTLN(F("AR: Analog Microphone (left channel only)."));
audioSource = new I2SAdcSource(SAMPLE_RATE, BLOCK_SIZE); audioSource = new I2SAdcSource(SAMPLE_RATE, BLOCK_SIZE);
delay(100); delay(100);
@@ -1235,10 +1234,25 @@ class AudioReactive : public Usermod {
if (audioSource) audioSource->initialize(audioPin); if (audioSource) audioSource->initialize(audioPin);
break; break;
#endif #endif
case 254: // dummy "network receive only" mode
if (audioSource) delete audioSource; audioSource = nullptr;
disableSoundProcessing = true;
audioSyncEnabled = 2; // force udp sound receive mode
enabled = true;
break;
case 255: // 255 = -1 = no audio source
// falls through to default
default:
if (audioSource) delete audioSource; audioSource = nullptr;
disableSoundProcessing = true;
enabled = false;
break;
} }
delay(250); // give microphone enough time to initialise delay(250); // give microphone enough time to initialise
if (!audioSource) enabled = false; // audio failed to initialise if (!audioSource && (dmType != 254)) enabled = false;// audio failed to initialise
#endif #endif
if (enabled) onUpdateBegin(false); // create FFT task, and initialize network if (enabled) onUpdateBegin(false); // create FFT task, and initialize network

View File

@@ -1687,10 +1687,11 @@ void WS2812FX::setTransitionMode(bool t) {
// rare circumstances are: setting FPS to high number (i.e. 120) and have very slow effect that will need more // 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 // time than 2 * _frametime (1000/FPS) to draw content
void WS2812FX::waitForIt() { void WS2812FX::waitForIt() {
unsigned long maxWait = millis() + 2*getFrameTime() + 100; // TODO: this needs a proper fix for timeout! see #4779 unsigned long waitStart = millis();
while (isServicing() && maxWait > millis()) delay(1); unsigned long maxWait = 2*getFrameTime() + 100; // TODO: this needs a proper fix for timeout! see #4779
while (isServicing() && (millis() - waitStart < maxWait)) delay(1); // safe even when millis() rolls over
#ifdef WLED_DEBUG #ifdef WLED_DEBUG
if (millis() >= maxWait) DEBUG_PRINTLN(F("Waited for strip to finish servicing.")); if (millis()-waitStart >= maxWait) DEBUG_PRINTLN(F("Waited for strip to finish servicing."));
#endif #endif
}; };

View File

@@ -213,7 +213,7 @@ function createTop(element, editor){
function httpPostCb(st,resp){ function httpPostCb(st,resp){
if (st!=200) alert("ERROR "+st+": "+resp); if (st!=200) alert("ERROR "+st+": "+resp);
else { else {
alert("Upload successful!"); showToast("Upload successful!");
refreshTree(); refreshTree();
} }
} }
@@ -264,7 +264,7 @@ function createTree(element, editor){
leaf.textContent=name; leaf.textContent=name;
var span = cE("span"); var span = cE("span");
span.style.cssText = "font-size: 14px; color: #aaa; margin-left: 8px;"; span.style.cssText = "font-size: 14px; color: #aaa; margin-left: 8px;";
span.textContent = (size / 1024).toFixed(1) + "KB"; span.textContent = (size > 0 ? Math.max(0.1, (size / 1024)).toFixed(1) : 0) + "KB"; // show size in KB, minimum 0.1 to not show 0KB for small files
leaf.appendChild(span); leaf.appendChild(span);
leaf.onmouseover=function(){ leaf.style.background="#333"; }; leaf.onmouseover=function(){ leaf.style.background="#333"; };
leaf.onmouseout=function(){ leaf.style.background=""; }; leaf.onmouseout=function(){ leaf.style.background=""; };
@@ -377,13 +377,13 @@ function prettyLedmap(json){
rows.push(" " + obj.map.slice(i, i + width).map(pad).join(", ")); rows.push(" " + obj.map.slice(i, i + width).map(pad).join(", "));
} }
let pretty = "{\n \"map\": [\n" + rows.join(",\n") + "\n ]"; let pretty = "{\n";
for (let k of Object.keys(obj)) { for (let k of Object.keys(obj)) {
if (k !== "map") { if (k !== "map") {
pretty += ",\n \"" + k + "\": " + JSON.stringify(obj[k]); pretty += " \"" + k + "\": " + JSON.stringify(obj[k]) + ",\n"; // print all keys first (speeds up loading)
} }
} }
pretty += "\n}"; pretty += " \"map\": [\n" + rows.join(",\n") + "\n ]\n}";
return pretty; return pretty;
} catch (e) { } catch (e) {
return json; return json;
@@ -493,7 +493,7 @@ function createEditor(element,file){
req.add("POST","/upload",fd,function(st,resp){ req.add("POST","/upload",fd,function(st,resp){
if (st!=200) alert("ERROR "+st+": "+resp); if (st!=200) alert("ERROR "+st+": "+resp);
else { else {
alert("File saved successfully!"); showToast("File saved");
refreshTree(); refreshTree();
} }
}); });
@@ -567,10 +567,18 @@ function onBodyLoad(){
var editor=createEditor("editor",vars.file); var editor=createEditor("editor",vars.file);
globalTree=createTree("tree",editor); globalTree=createTree("tree",editor);
createTop("top",editor); createTop("top",editor);
// Add Ctrl+S / Cmd+S override to save the file
document.addEventListener('keydown', function(e) {
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
e.preventDefault();
editor.save();
}
});
} }
</script> </script>
</head> </head>
<body onload="onBodyLoad()"> <body onload="onBodyLoad()">
<div id="toast"></div>
<div id="loader"><div class="loader"></div></div> <div id="loader"><div class="loader"></div></div>
<div id="top"></div> <div id="top"></div>
<div style="flex:1;position:relative"> <div style="flex:1;position:relative">

View File

@@ -401,6 +401,8 @@ uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxL
int16_t extractModeDefaults(uint8_t mode, const char *segVar); int16_t extractModeDefaults(uint8_t mode, const char *segVar);
void checkSettingsPIN(const char *pin); void checkSettingsPIN(const char *pin);
uint16_t crc16(const unsigned char* data_p, size_t length); uint16_t crc16(const unsigned char* data_p, size_t length);
String computeSHA1(const String& input);
String getDeviceId();
uint16_t beatsin88_t(accum88 beats_per_minute_88, uint16_t lowest = 0, uint16_t highest = 65535, uint32_t timebase = 0, uint16_t phase_offset = 0); uint16_t beatsin88_t(accum88 beats_per_minute_88, uint16_t lowest = 0, uint16_t highest = 65535, uint32_t timebase = 0, uint16_t phase_offset = 0);
uint16_t beatsin16_t(accum88 beats_per_minute, uint16_t lowest = 0, uint16_t highest = 65535, uint32_t timebase = 0, uint16_t phase_offset = 0); uint16_t beatsin16_t(accum88 beats_per_minute, uint16_t lowest = 0, uint16_t highest = 65535, uint32_t timebase = 0, uint16_t phase_offset = 0);
uint8_t beatsin8_t(accum88 beats_per_minute, uint8_t lowest = 0, uint8_t highest = 255, uint32_t timebase = 0, uint8_t phase_offset = 0); uint8_t beatsin8_t(accum88 beats_per_minute, uint8_t lowest = 0, uint8_t highest = 255, uint32_t timebase = 0, uint8_t phase_offset = 0);

View File

@@ -1,5 +1,6 @@
#include "wled.h" #include "wled.h"
#define JSON_PATH_STATE 1 #define JSON_PATH_STATE 1
#define JSON_PATH_INFO 2 #define JSON_PATH_INFO 2
#define JSON_PATH_STATE_INFO 3 #define JSON_PATH_STATE_INFO 3
@@ -690,6 +691,7 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme
} }
} }
void serializeInfo(JsonObject root) void serializeInfo(JsonObject root)
{ {
root[F("ver")] = versionString; root[F("ver")] = versionString;
@@ -697,6 +699,7 @@ void serializeInfo(JsonObject root)
root[F("cn")] = F(WLED_CODENAME); root[F("cn")] = F(WLED_CODENAME);
root[F("release")] = releaseString; root[F("release")] = releaseString;
root[F("repo")] = repoString; root[F("repo")] = repoString;
root[F("deviceId")] = getDeviceId();
JsonObject leds = root.createNestedObject(F("leds")); JsonObject leds = root.createNestedObject(F("leds"));
leds[F("count")] = strip.getLengthTotal(); leds[F("count")] = strip.getLengthTotal();

View File

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