diff --git a/wled00/const.h b/wled00/const.h
index 8891dfcaee..01ddee4f16 100644
--- a/wled00/const.h
+++ b/wled00/const.h
@@ -439,6 +439,23 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
#define ERR_OVERCURRENT 31 // An attached current sensor has measured a current above the threshold (not implemented)
#define ERR_UNDERVOLT 32 // An attached voltmeter has measured a voltage below the threshold (not implemented)
+// Additional error types for better user feedback
+#define ERR_NORAM_BUS 33 // Not enough RAM for bus allocation
+#define ERR_NORAM_SEG 34 // Not enough RAM for segment allocation
+#define ERR_NORAM_TRANS 35 // Not enough RAM for transition effects
+#define ERR_PIN_CONFLICT 36 // Pin assignment conflict detected
+#define ERR_PIN_INVALID 37 // Invalid pin number for this platform
+#define ERR_CONFIG_LOAD 38 // Configuration loading failed
+#define ERR_CONFIG_SAVE 39 // Configuration saving failed
+
+// Warning types (starting at 100 as requested)
+#define WARN_LOW_MEMORY 100 // Low memory warning
+#define WARN_HIGH_TEMP 101 // Temperature approaching limits
+#define WARN_LOW_VOLTAGE 102 // Voltage below optimal range
+#define WARN_HIGH_CURRENT 103 // Current approaching limits
+#define WARN_WIFI_WEAK 104 // Weak WiFi signal
+#define WARN_FS_SPACE 105 // Filesystem space running low
+
// Timer mode types
#define NL_MODE_SET 0 //After nightlight time elapsed, set to target brightness
#define NL_MODE_FADE 1 //Fade to target brightness gradually
diff --git a/wled00/data/index.htm b/wled00/data/index.htm
index 22f1987e93..9e8910eb28 100644
--- a/wled00/data/index.htm
+++ b/wled00/data/index.htm
@@ -325,6 +325,13 @@
Loading...
diff --git a/wled00/data/index.js b/wled00/data/index.js
index 2514f03fb1..b5c65bf6f8 100644
--- a/wled00/data/index.js
+++ b/wled00/data/index.js
@@ -26,6 +26,8 @@ var pmt = 1, pmtLS = 0, pmtLast = 0;
var lastinfo = {};
var isM = false, mw = 0, mh=0;
var ws, wsRpt=0;
+var errorLog = []; // Store last 5 errors/warnings
+var hasUnreadErrors = false;
var cfg = {
theme:{base:"dark", bg:{url:"", rnd: false, rndGrayscale: false, rndBlur: false}, alpha:{bg:0.6,tab:0.8}, color:{bg:""}},
comp :{colors:{picker: true, rgb: false, quick: true, hex: false},
@@ -384,6 +386,152 @@ function inforow(key, val, unit = "")
return `
${key} | ${val}${unit} |
`;
}
+function getErrorMessage(errorCode) {
+ switch (errorCode) {
+ case 1: return "Operation denied by current settings";
+ case 2: return "Cannot process request while client is active";
+ case 3: return "JSON buffer is locked, try again in a moment";
+ case 4: return "Feature not implemented on this version";
+ case 7: return "Insufficient RAM to allocate pixel buffer";
+ case 8: return "Not enough RAM available for effect processing";
+ case 9: return "JSON parsing failed - data may be too large";
+ case 10: return "Could not initialize filesystem - check partition";
+ case 11: return "Not enough space to save preset to filesystem";
+ case 12: return "Requested preset does not exist";
+ case 13: return "IR configuration file 'ir.json' not found";
+ case 14: return "Remote configuration file 'remote.json' not found";
+ case 19: return "An unspecified filesystem error occurred";
+ case 30: return "Temperature sensor reading above safe threshold";
+ case 31: return "Current sensor reading above safe threshold";
+ case 32: return "Voltage sensor reading below safe threshold";
+ case 33: return "Insufficient RAM to allocate LED bus";
+ case 34: return "Insufficient RAM to allocate segment data";
+ case 35: return "Insufficient RAM for transition effects";
+ case 36: return "Pin assignment conflict detected";
+ case 37: return "Invalid pin number for this platform";
+ case 38: return "Failed to load configuration from filesystem";
+ case 39: return "Failed to save configuration to filesystem";
+ case 100: return "Memory usage is approaching limits";
+ case 101: return "Temperature is approaching safe limits";
+ case 102: return "Voltage is below optimal operating range";
+ case 103: return "Current draw is approaching safe limits";
+ case 104: return "WiFi signal strength is poor";
+ case 105: return "Filesystem space is running low";
+ default: return `Unknown error code ${errorCode}`;
+ }
+}
+
+function addToErrorLog(errorCode, timestamp = null) {
+ if (!timestamp) timestamp = Date.now();
+
+ const errorEntry = {
+ code: errorCode,
+ message: getErrorMessage(errorCode),
+ timestamp: timestamp,
+ isWarning: errorCode >= 100
+ };
+
+ // Add to beginning of array
+ errorLog.unshift(errorEntry);
+
+ // Keep only last 5 entries
+ if (errorLog.length > 5) {
+ errorLog = errorLog.slice(0, 5);
+ }
+
+ hasUnreadErrors = true;
+ updateInfoButtonIcon();
+}
+
+function updateInfoButtonIcon() {
+ const infoBtn = gId('buttonI');
+ const icon = infoBtn.querySelector('i');
+ if (hasUnreadErrors) {
+ // Change to red exclamation mark icon
+ icon.innerHTML = ''; // Use add/warning icon
+ icon.style.color = 'var(--c-r)';
+ } else {
+ // Reset to normal info icon
+ icon.innerHTML = ''; // Info icon
+ icon.style.color = '';
+ }
+}
+
+function handleServerErrorLog(serverErrors, serverTime) {
+ // Clear client-side log and replace with server data
+ errorLog = [];
+ hasUnreadErrors = false;
+
+ for (let i = 0; i < serverErrors.length; i++) {
+ const serverEntry = serverErrors[i];
+ // Calculate absolute timestamp using server time and error timestamp
+ const absoluteTime = Date.now() - (serverTime - serverEntry.t);
+
+ const errorEntry = {
+ code: serverEntry.c,
+ // Use custom message if provided, otherwise use error code lookup
+ message: serverEntry.m ? serverEntry.m : getErrorMessage(serverEntry.c),
+ timestamp: absoluteTime,
+ // If custom message provided, determine warning/error based on code range
+ isWarning: serverEntry.c >= 100,
+ tag1: serverEntry.t1 || 0,
+ tag2: serverEntry.t2 || 0,
+ tag3: serverEntry.t3 || 0
+ };
+
+ errorLog.push(errorEntry);
+ }
+
+ if (errorLog.length > 0) {
+ hasUnreadErrors = true;
+ }
+
+ updateInfoButtonIcon();
+}
+
+function clearErrorLog() {
+ // Send clear command to server
+ fetch(getURL('/json/state'), {
+ method: 'post',
+ body: JSON.stringify({clrErrLog: true}),
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ })
+ .then(res => {
+ // Clear local state
+ errorLog = [];
+ hasUnreadErrors = false;
+ updateInfoButtonIcon();
+ const errorArea = gId('errorLogArea');
+ if (errorArea) {
+ errorArea.style.display = 'none';
+ }
+ })
+ .catch((error) => {
+ console.log('Error clearing log:', error);
+ });
+}
+
+function generateErrorLogHtml() {
+ if (errorLog.length === 0) return '';
+
+ let html = '';
+ for (let i = 0; i < errorLog.length; i++) {
+ const entry = errorLog[i];
+ // Use 24h format without seconds
+ const timeStr = new Date(entry.timestamp).toLocaleTimeString([], {hour12: false, hour: '2-digit', minute: '2-digit'});
+ const prefix = entry.isWarning ? 'Warning' : 'Error';
+ const color = entry.isWarning ? 'var(--c-y)' : 'var(--c-r)';
+
+ html += `
+ ${timeStr} ${prefix} ${entry.code}: ${entry.message}
+
`;
+ }
+
+ return html;
+}
+
function getLowestUnusedP()
{
var l = 1;
@@ -746,7 +894,17 @@ ${inforow("Flash size",i.flash," MB")}
${inforow("Filesystem",i.fs.u + "/" + i.fs.t + " kB (" +Math.round(i.fs.u*100/i.fs.t) + "%)")}
${inforow("Environment",i.arch + " " + i.core + " (" + i.lwip + ")")}
`;
+
gId('kv').innerHTML = cn;
+
+ // Update error log area visibility and content
+ const errorArea = gId('errorLogArea');
+ if (errorLog.length > 0) {
+ errorArea.style.display = 'block';
+ gId('errorLogContent').innerHTML = generateErrorLogHtml();
+ } else {
+ errorArea.style.display = 'none';
+ }
// update all sliders in Info
d.querySelectorAll('#kv .sliderdisplay').forEach((sd,i) => {
let s = sd.previousElementSibling;
@@ -1518,40 +1676,13 @@ function readState(s,command=false)
gId('checkO3').checked = !(!i.o3);
if (s.error && s.error != 0) {
- var errstr = "";
- switch (s.error) {
- case 1:
- errstr = "Denied!";
- break;
- case 3:
- errstr = "Buffer locked!";
- break;
- case 7:
- errstr = "No RAM for buffer!";
- break;
- case 8:
- errstr = "Effect RAM depleted!";
- break;
- case 9:
- errstr = "JSON parsing error!";
- break;
- case 10:
- errstr = "Could not mount filesystem!";
- break;
- case 11:
- errstr = "Not enough space to save preset!";
- break;
- case 12:
- errstr = "Preset not found.";
- break;
- case 13:
- errstr = "Missing ir.json.";
- break;
- case 19:
- errstr = "A filesystem error has occured.";
- break;
- }
- showToast('Error ' + s.error + ": " + errstr, true);
+ // Add to error log for detailed tracking
+ addToErrorLog(s.error);
+ }
+
+ // Handle server-side error log
+ if (s.errorLog && s.errorLogTime) {
+ handleServerErrorLog(s.errorLog, s.errorLogTime);
}
selectedPal = i.pal;
@@ -1846,7 +1977,12 @@ function toggleInfo()
if (isNodes) toggleNodes();
if (isLv && isM) toggleLiveview();
isInfo = !isInfo;
- if (isInfo) requestJson();
+ if (isInfo) {
+ requestJson();
+ // Mark errors as read when info panel is opened
+ hasUnreadErrors = false;
+ updateInfoButtonIcon();
+ }
gId('info').style.transform = (isInfo) ? "translateY(0px)":"translateY(100%)";
gId('buttonI').className = (isInfo) ? "active":"";
}
diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h
index 1d81655d6d..e9a3b14150 100644
--- a/wled00/fcn_declare.h
+++ b/wled00/fcn_declare.h
@@ -555,4 +555,10 @@ void sendDataWs(AsyncWebSocketClient * client = nullptr);
void XML_response(Print& dest);
void getSettingsJS(byte subPage, Print& dest);
+//util.cpp - error logging
+void addToErrorLog(byte errorCode, byte tag1 = 0, byte tag2 = 0, byte tag3 = 0, const char* customMessage = nullptr);
+void clearErrorLog();
+byte getErrorLogCount();
+const struct ErrorLogEntry& getErrorLogEntry(byte index);
+
#endif
diff --git a/wled00/json.cpp b/wled00/json.cpp
index d2b771c590..06106dd95f 100644
--- a/wled00/json.cpp
+++ b/wled00/json.cpp
@@ -367,6 +367,11 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
#if defined(WLED_DEBUG) && defined(WLED_DEBUG_HOST)
netDebugEnabled = root[F("debug")] | netDebugEnabled;
#endif
+
+ // Handle clear error log command
+ if (root[F("clrErrLog")]) {
+ clearErrorLog();
+ }
bool onBefore = bri;
getVal(root["bri"], bri);
@@ -639,7 +644,31 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme
}
if (!forPreset) {
- if (errorFlag) {root[F("error")] = errorFlag; errorFlag = ERR_NONE;} //prevent error message to persist on screen
+ if (errorFlag) {
+ root[F("error")] = errorFlag;
+ addToErrorLog(errorFlag); // Add to error log
+ errorFlag = ERR_NONE; // Reset error flag
+ }
+
+ // Add error log to JSON response
+ if (getErrorLogCount() > 0) {
+ JsonArray errors = root.createNestedArray(F("errorLog"));
+
+ for (byte i = 0; i < getErrorLogCount(); i++) {
+ const ErrorLogEntry& entry = getErrorLogEntry(i);
+ JsonObject err = errors.createNestedObject();
+ err[F("t")] = entry.timestamp;
+ err[F("c")] = entry.errorCode;
+ err[F("t1")] = entry.tag1;
+ err[F("t2")] = entry.tag2;
+ err[F("t3")] = entry.tag3;
+ // Add custom message if present
+ if (entry.customMessage != nullptr) {
+ err[F("m")] = entry.customMessage;
+ }
+ }
+ root[F("errorLogTime")] = millis(); // Current time for client calculations
+ }
root["ps"] = (currentPreset > 0) ? currentPreset : -1;
root[F("pl")] = currentPlaylist;
diff --git a/wled00/util.cpp b/wled00/util.cpp
index 8aaaf34cab..6b7c163cb8 100644
--- a/wled00/util.cpp
+++ b/wled00/util.cpp
@@ -1124,4 +1124,73 @@ uint8_t perlin8(uint16_t x, uint16_t y) {
uint8_t perlin8(uint16_t x, uint16_t y, uint16_t z) {
return (((perlin3D_raw((uint32_t)x << 8, (uint32_t)y << 8, (uint32_t)z << 8, true) * 2015) >> 10) + 33168) >> 8; //scale to 16 bit, offset, then scale to 8bit
+}
+
+// Error logging system
+struct ErrorLogEntry {
+ unsigned long timestamp; // millis() when error occurred
+ byte errorCode; // error number (8bit)
+ byte tag1; // future use tag 1
+ byte tag2; // future use tag 2
+ byte tag3; // future use tag 3
+ char* customMessage; // optional custom message string (nullptr if not used)
+};
+
+#define ERROR_LOG_SIZE 5
+static ErrorLogEntry errorLog[ERROR_LOG_SIZE];
+static byte errorLogIndex = 0;
+static byte errorLogCount = 0;
+
+// Error logging functions
+void addToErrorLog(byte errorCode, byte tag1, byte tag2, byte tag3, const char* customMessage) {
+ // Free existing custom message if present
+ if (errorLog[errorLogIndex].customMessage != nullptr) {
+ delete[] errorLog[errorLogIndex].customMessage;
+ errorLog[errorLogIndex].customMessage = nullptr;
+ }
+
+ errorLog[errorLogIndex].timestamp = millis();
+ errorLog[errorLogIndex].errorCode = errorCode;
+ errorLog[errorLogIndex].tag1 = tag1;
+ errorLog[errorLogIndex].tag2 = tag2;
+ errorLog[errorLogIndex].tag3 = tag3;
+
+ // Copy custom message if provided
+ if (customMessage != nullptr) {
+ size_t len = strlen(customMessage);
+ errorLog[errorLogIndex].customMessage = new char[len + 1];
+ if (errorLog[errorLogIndex].customMessage != nullptr) {
+ strcpy(errorLog[errorLogIndex].customMessage, customMessage);
+ }
+ } else {
+ errorLog[errorLogIndex].customMessage = nullptr;
+ }
+
+ errorLogIndex = (errorLogIndex + 1) % ERROR_LOG_SIZE;
+ if (errorLogCount < ERROR_LOG_SIZE) {
+ errorLogCount++;
+ }
+}
+
+void clearErrorLog() {
+ // Free all custom message strings
+ for (byte i = 0; i < ERROR_LOG_SIZE; i++) {
+ if (errorLog[i].customMessage != nullptr) {
+ delete[] errorLog[i].customMessage;
+ errorLog[i].customMessage = nullptr;
+ }
+ }
+
+ errorLogIndex = 0;
+ errorLogCount = 0;
+}
+
+// Helper functions to read error log
+byte getErrorLogCount() {
+ return errorLogCount;
+}
+
+const ErrorLogEntry& getErrorLogEntry(byte index) {
+ byte actualIndex = (errorLogIndex + ERROR_LOG_SIZE - errorLogCount + index) % ERROR_LOG_SIZE;
+ return errorLog[actualIndex];
}
\ No newline at end of file