Skip to content
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 124 additions & 43 deletions wled00/cfg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -679,37 +679,111 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(macroCountdown, cntdwn["macro"]);
setCountdown();

JsonArray timers = tm["ins"];
uint8_t it = 0;
for (JsonObject timer : timers) {
if (it > 9) break;
if (it<8 && timer[F("hour")]==255) it=8; // hour==255 -> sunrise/sunset
CJSON(timerHours[it], timer[F("hour")]);
CJSON(timerMinutes[it], timer["min"]);
CJSON(timerMacro[it], timer["macro"]);

byte dowPrev = timerWeekday[it];
//note: act is currently only 0 or 1.
//the reason we are not using bool is that the on-disk type in 0.11.0 was already int
int actPrev = timerWeekday[it] & 0x01;
CJSON(timerWeekday[it], timer[F("dow")]);
if (timerWeekday[it] != dowPrev) { //present in JSON
timerWeekday[it] <<= 1; //add active bit
int act = timer["en"] | actPrev;
if (act) timerWeekday[it]++;
JsonArray timersArray = tm["ins"];

// Check if this is a new format (vector-based) or old format (array-based)
// If timers array exists in JSON, load from it
if (!timersArray.isNull()) {
clearTimers();

for (JsonObject timer : timersArray) {
uint8_t hour = timer[F("hour")] | 0;
int8_t minute = timer[F("min")] | 0;
uint8_t preset = timer[F("macro")] | 0;

// Build weekdays byte: bit 0 is enabled, bits 1-7 are weekdays
uint8_t dow = timer[F("dow")] | 127; // default all days enabled
int enabled = timer[F("en")] | 0;
uint8_t weekdays = (dow << 1) | (enabled ? 1 : 0);

// Get date range (only for regular timers)
uint8_t monthStart = 1, monthEnd = 12, dayStart = 1, dayEnd = 31;
if (hour < TIMER_HOUR_SUNSET) { // Regular timer
JsonObject start = timer[F("start")];
JsonObject end = timer[F("end")];
if (!start.isNull()) {
monthStart = start[F("mon")] | 1;
dayStart = start[F("day")] | 1;
}
if (!end.isNull()) {
monthEnd = end[F("mon")] | 12;
dayEnd = end[F("day")] | 31;
}
}

// Add timer (validation happens in addTimer)
addTimer(preset, hour, minute, weekdays, monthStart, monthEnd, dayStart, dayEnd);
}
} else {
// No timers.ins array - check for old JSON format or legacy arrays
// Try to read old JSON format first (if timers were stored as individual fields)
bool foundOldJsonTimers = false;

// Check for old JSON format: timers stored as arrays or individual fields
// Format could be: "timers": {"0": {...}, "1": {...}, ...} or arrays
JsonArray timerHoursJson = tm["hour"];
JsonArray timerMinutesJson = tm["min"];
JsonArray timerMacroJson = tm["macro"];
JsonArray timerWeekdayJson = tm["dow"];
JsonArray timerMonthJson = tm["month"];
JsonArray timerDayJson = tm["day"];
JsonArray timerDayEndJson = tm["dayend"];

// If we have arrays in the old format, migrate from them
if (!timerHoursJson.isNull() && !timerMacroJson.isNull()) {
clearTimers();
foundOldJsonTimers = true;

DEBUG_PRINTLN(F("Migrating timers from old JSON array format..."));

// Migrate up to 10 timers (8 regular + sunrise + sunset)
for (unsigned i = 0; i < 10 && i < timerHoursJson.size(); i++) {
uint8_t hour = timerHoursJson[i] | 0;
int8_t minute = timerMinutesJson.isNull() ? 0 : (timerMinutesJson[i] | 0);
uint8_t preset = timerMacroJson[i] | 0;
uint8_t weekdays = timerWeekdayJson.isNull() ? 255 : (timerWeekdayJson[i] | 255);

// Skip empty timers
if (preset == 0 && hour == 0 && minute == 0) continue;

// Handle sunrise (index 8) and sunset (index 9) timers specially
if (i == 8) hour = TIMER_HOUR_SUNRISE;
else if (i == 9) hour = TIMER_HOUR_SUNSET;

// Extract date range for regular timers (indices 0-7)
uint8_t monthStart = 1, monthEnd = 12, dayStart = 1, dayEnd = 31;
if (hour <= 24 && !timerMonthJson.isNull()) {
uint8_t monthPacked = timerMonthJson[i] | 28; // default 0x1C (month 1-12)
monthStart = (monthPacked >> 4) & 0x0F;
monthEnd = monthPacked & 0x0F;
if (monthStart == 0) monthStart = 1;
if (monthEnd == 0) monthEnd = 12;

if (!timerDayJson.isNull()) dayStart = timerDayJson[i] | 1;
if (!timerDayEndJson.isNull()) dayEnd = timerDayEndJson[i] | 31;
}

addTimer(preset, hour, minute, weekdays, monthStart, monthEnd, dayStart, dayEnd);
}

DEBUG_PRINTF("Old JSON migration complete: %d timers loaded\n", timers.size());
}
if (it<8) {
JsonObject start = timer["start"];
byte startm = start["mon"];
if (startm) timerMonth[it] = (startm << 4);
CJSON(timerDay[it], start["day"]);
JsonObject end = timer["end"];
CJSON(timerDayEnd[it], end["day"]);
byte endm = end["mon"];
if (startm) timerMonth[it] += endm & 0x0F;
if (!(timerMonth[it] & 0x0F)) timerMonth[it] += 12; //default end month to 12

// If no old JSON format found, check legacy arrays in memory
if (!foundOldJsonTimers) {
// Check if legacy arrays have data and migrate if needed
// Original layout: 0-7 regular timers, 8 sunrise, 9 sunset (10 total)
bool hasLegacyData = false;
for (unsigned i = 0; i < 10; i++) {
if (timerMacro[i] != 0 || timerHours[i] != 0 || timerMinutes[i] != 0) {
hasLegacyData = true;
break;
}
}
if (hasLegacyData) {
migrateTimersFromArrays();
}
}
it++;
}

JsonObject ota = doc["ota"];
Expand Down Expand Up @@ -1207,21 +1281,28 @@ void serializeConfig(JsonObject root) {

JsonArray timers_ins = timers.createNestedArray("ins");

for (unsigned i = 0; i < 10; i++) {
if (timerMacro[i] == 0 && timerHours[i] == 0 && timerMinutes[i] == 0) continue; // sunrise/sunset get saved always (timerHours=255)
// Save timers from vector
for (size_t i = 0; i < ::timers.size(); i++) {
const Timer& timer = ::timers[i];

// Skip timers with preset 0 (disabled) unless they have other meaningful data
if (timer.preset == 0 && timer.hour == 0 && timer.minute == 0) continue;

JsonObject timers_ins0 = timers_ins.createNestedObject();
timers_ins0["en"] = (timerWeekday[i] & 0x01);
timers_ins0[F("hour")] = timerHours[i];
timers_ins0["min"] = timerMinutes[i];
timers_ins0["macro"] = timerMacro[i];
timers_ins0[F("dow")] = timerWeekday[i] >> 1;
if (i<8) {
JsonObject start = timers_ins0.createNestedObject("start");
start["mon"] = (timerMonth[i] >> 4) & 0xF;
start["day"] = timerDay[i];
JsonObject end = timers_ins0.createNestedObject("end");
end["mon"] = timerMonth[i] & 0xF;
end["day"] = timerDayEnd[i];
timers_ins0[F("en")] = timer.isEnabled() ? 1 : 0;
timers_ins0[F("hour")] = timer.hour;
timers_ins0[F("min")] = timer.minute;
timers_ins0[F("macro")] = timer.preset;
timers_ins0[F("dow")] = timer.weekdays >> 1;

// Only save date range for regular timers (not sunrise/sunset)
if (timer.isRegular()) {
JsonObject start = timers_ins0.createNestedObject(F("start"));
start[F("mon")] = timer.monthStart;
start[F("day")] = timer.dayStart;
JsonObject end = timers_ins0.createNestedObject(F("end"));
end[F("mon")] = timer.monthEnd;
end[F("day")] = timer.dayEnd;
}
}

Expand Down
2 changes: 2 additions & 0 deletions wled00/const.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ constexpr size_t FIXED_PALETTE_COUNT = DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_C
#define WLED_MAX_DIGITAL_CHANNELS 3
#define WLED_MAX_ANALOG_CHANNELS 5
#define WLED_MIN_VIRTUAL_BUSSES 3 // no longer used for bus creation but used to distinguish S2/S3 in UI
#define WLED_MAX_TIMERS 16 // reduced limit for ESP8266 due to memory constraints
#else
#if !defined(LEDC_CHANNEL_MAX) || !defined(LEDC_SPEED_MODE_MAX)
#include "driver/ledc.h" // needed for analog/LEDC channel counts
Expand All @@ -81,6 +82,7 @@ constexpr size_t FIXED_PALETTE_COUNT = DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_C
//#define WLED_MAX_ANALOG_CHANNELS 16
#define WLED_MIN_VIRTUAL_BUSSES 6 // no longer used for bus creation but used to distinguish S2/S3 in UI
#endif
#define WLED_MAX_TIMERS 64 // maximum number of timers
#endif
// WLED_MAX_BUSSES was used to define the size of busses[] array which is no longer needed
// instead it will help determine max number of buses that can be defined at compile time
Expand Down
Loading