Implement lastPreset tracking for auto-save usermod#5389
Implement lastPreset tracking for auto-save usermod#5389ampledashes wants to merge 1 commit intowled:mainfrom
Conversation
Added lastPreset variable to track the last preset used for auto-saving functionality so that the last preset active will be called upon reboot
WalkthroughAdds preset-recall detection to the auto-save usermod by tracking the last recalled preset and extending the autosave settle window when a new preset is recalled, preventing premature autosave triggers during preset switches. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
usermods/usermod_v2_auto_save/usermod_v2_auto_save.cpp (1)
134-144:⚠️ Potential issue | 🟠 MajorPreset-recall tracking is unreachable with default
autoSaveIgnorePresets=trueThe early-return guard at line 134 fires for any
currentPreset > 0whenautoSaveIgnorePresetsistrue(the default since line 56). The new block at lines 141–144 is therefore dead code in the default configuration — it is only reached whencurrentPreset == 0, i.e. no preset is active. The feature only works when users explicitly setautoSaveIgnorePresets = false, which is the non-default, undocumented path.A secondary consequence: because
lastPresetis never updated during the early-return path, it goes stale. When a user recalls preset N (early return fires,lastPresetunchanged) and then makes a manual change that clearscurrentPresetto 0, the comparisonlastPreset (N) != currentPreset (0)fires a spurious "preset change" event. The practical impact is limited — the brightness/speed/etc. change already setsautoSaveAfterindependently — butlastPresetshould still be kept consistent.Suggested fix: move the
lastPresetsynchronisation (and conditionallyautoSaveAfterextension) before the early-return guard, or restructure the guard to allow preset-recall tracking to proceed regardless ofautoSaveIgnorePresets:🐛 Proposed fix
void loop() { static unsigned long lastRun = 0; unsigned long now = millis(); if (!autoSaveAfterSec || !enabled || (strip.isUpdating() && now - lastRun < 240)) return; lastRun = now; uint8_t currentMode = strip.getMainSegment().mode; uint8_t currentPalette = strip.getMainSegment().palette; unsigned long wouldAutoSaveAfter = now + autoSaveAfterSec*1000; - if (!autoSaveAfterSec || !enabled || (autoSaveIgnorePresets && currentPreset>0) || (strip.isUpdating() && now - lastRun < 240)) return; // setting 0 as autosave seconds disables autosave - lastRun = now; - uint8_t currentMode = strip.getMainSegment().mode; - uint8_t currentPalette = strip.getMainSegment().palette; - unsigned long wouldAutoSaveAfter = now + autoSaveAfterSec*1000; - - // Trigger on preset recall - if (lastPreset != currentPreset) { - lastPreset = currentPreset; - autoSaveAfter = wouldAutoSaveAfter; - } - - if (knownBrightness != bri) { + // Track preset recalls before the autoSaveIgnorePresets guard so lastPreset + // is always consistent and the settle window is extended on any preset switch. + if (lastPreset != currentPreset) { + lastPreset = currentPreset; + autoSaveAfter = wouldAutoSaveAfter; + } + + if (autoSaveIgnorePresets && currentPreset > 0) return; // preset active, skip save + + if (knownBrightness != bri) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@usermods/usermod_v2_auto_save/usermod_v2_auto_save.cpp` around lines 134 - 144, The preset-recall update is unreachable when autoSaveIgnorePresets is true because the early-return uses (autoSaveIgnorePresets && currentPreset>0); fix by moving the preset-change detection/synchronization before that guard: compute wouldAutoSaveAfter, check if lastPreset != currentPreset, update lastPreset and (only if you want to extend autosave regardless of ignore flag) set autoSaveAfter = wouldAutoSaveAfter as appropriate, then evaluate the existing early-return (which can still skip extending autosave when autoSaveIgnorePresets is true). Ensure you reference and update the same variables: lastPreset, currentPreset, autoSaveAfter, wouldAutoSaveAfter, and keep the strip.isUpdating()/now-lastRun check intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@usermods/usermod_v2_auto_save/usermod_v2_auto_save.cpp`:
- Around line 134-144: The preset-recall update is unreachable when
autoSaveIgnorePresets is true because the early-return uses
(autoSaveIgnorePresets && currentPreset>0); fix by moving the preset-change
detection/synchronization before that guard: compute wouldAutoSaveAfter, check
if lastPreset != currentPreset, update lastPreset and (only if you want to
extend autosave regardless of ignore flag) set autoSaveAfter =
wouldAutoSaveAfter as appropriate, then evaluate the existing early-return
(which can still skip extending autosave when autoSaveIgnorePresets is true).
Ensure you reference and update the same variables: lastPreset, currentPreset,
autoSaveAfter, wouldAutoSaveAfter, and keep the strip.isUpdating()/now-lastRun
check intact.
|
@ampledashes I'm still struggling to understand the use case for this PR. Can you explain a bit more in which scenarios this will be helpful? The usermod already tracks the current effect, palette, effect sliders, colors, brightness. Afaik the purpose is not to create a copy of the last active preset, but to help users who tinker with effects and want the same effect to be active on next start.
This is exactly something that will not happen: If you activate preset 37, then switch WLED off after a minute, WLED will not re-activate preset 37. It will boot into the autosave preset, let's say preset 250. Maybe I missed something? |
|
The specific gap we’re addressing is that recalling a preset and then powering off before making any value changes results in nothing being saved — the autosave never triggered because no tracked values changed. On reboot, the device loads stale autosave state rather than the recently recalled preset’s state. This PR ensures preset recalls also schedule a save, so the autosave snapshot stays current. |
does that not work? |
Ah OK, so technically the issue is that this UM is not multi-segment aware. It tracks changes to the global "currenteffect", "currentPalette" etc. and reacts on changes in the main segment, but changing the effect in the second, third, etc segment will not be recognised. The same happens when changing to a preset that doesn't modify the first segment, but only affects other segments. Adding "currentPreset" to the watched state information will be an improvement, but it's still not a solution when users change the effect in the second segment without using a preset. Plus it will add flash writes (wearing out flash) that are just copying the current preset state into the dedicated autosave preset. |
|
|
@coderabbitai please re-review this PR. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 11775
Re-review Summary — PR
|
| Priority | Issue | Reviewer |
|---|---|---|
| 1 | No new opt-in setting — behavior silently conflicts with existing autoSaveIgnorePresets |
softhack007 |
| 2 | Playlist scenario: if a playlist cycles presets every few seconds, each step reschedules the settle window, potentially causing continuous back-to-back saves | softhack007 |
| 3 | JSON-controlled presets (e.g. button/timer with LO=2) would also trigger saves unintentionally |
softhack007 |
| 4 | autoSaveIgnorePresets=false already partially addresses the stated use case — the PR adds complexity without clearly superseding the existing mechanism |
DedeHai |
| 5 | Multi-segment awareness gap (pre-existing, not introduced here): only the main segment is watched | softhack007 |
✅ Suggested Improvements (prioritized)
-
Add a dedicated opt-in setting, e.g.
autoSaveOnPresetRecall(bool, defaultfalse), exposed in the config. Guard the preset-recall block with it. This keeps existing behaviour intact and gives users explicit control. -
Restructure the guard so
lastPresetis always updated (to avoid drift), even when a save is not scheduled:
// Always sync lastPreset to avoid drift, regardless of other guards
if (lastPreset != currentPreset) {
lastPreset = currentPreset;
if (autoSaveOnPresetRecall && enabled && autoSaveAfterSec)
autoSaveAfter = now + autoSaveAfterSec * 1000UL;
}
if (!autoSaveAfterSec || !enabled || (autoSaveIgnorePresets && currentPreset > 0) || ...) return;- Sync
known*values after a preset recall to prevent a redundant second save:
if (lastPreset != currentPreset) {
lastPreset = currentPreset;
// Sync known values so change-detection doesn't double-fire
knownBrightness = bri;
knownEffectSpeed = effectSpeed;
knownEffectIntensity = effectIntensity;
knownMode = strip.getMainSegment().mode;
knownPalette = strip.getMainSegment().palette;
if (autoSaveOnPresetRecall) autoSaveAfter = wouldAutoSaveAfter;
}-
Throttle or debounce playlist saves — if
currentPresetchanges more than once within the settle window, only the last change should matter (which the settle-window approach already handles, but only if the save is not prematurely fired between playlist steps). -
Document the feature in the file-header comment block, alongside the existing bullet list of tracked state.
🧠 Learnings used
Learnt from: BobLoeffler68
Repo: wled/WLED PR: 5440
File: usermods/user_fx/user_fx.cpp:1307-1327
Timestamp: 2026-03-24T07:59:28.661Z
Learning: In WLED's Dissolve Plus effect (usermods/user_fx/user_fx.cpp, mode_dissolveplus), when the "Last one" checkbox (check3/lastOneMode) is turned off while the effect is in PHASE_PAUSE_SURVIVOR, the phase is intentionally NOT immediately reset. The pause expires naturally via the normal delay timing (a few hundred ticks), after which the filling phase begins. PHASE_DISSOLVE_SURVIVOR is reset to PHASE_DISSOLVE and PHASE_FILL_SURVIVOR is reset to PHASE_FILL on toggle-off, but PHASE_PAUSE_SURVIVOR is left to expire on its own. Do not flag this as a bug in future reviews.
Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2026-03-08T00:57:36.134Z
Learning: In WLED (wled00/cfg.cpp), `deserializeConfigFromFS()` only sets `needsSave=true` (triggering a write of cfg.json) when usermod settings are present in the JSON (lines 758-761). On a fresh install with no usermods, `needsSave=false` so `serializeConfigToFS()` is never called at first boot. Correct defaults are never written to cfg.json until the user manually saves. Structural fix: set `needsSave = needsSave || !cfgExists` where `cfgExists = WLED_FS.exists(s_cfg_json)` checked before reading the file.
Learnt from: DedeHai
Repo: wled/WLED PR: 4798
File: wled00/FX.cpp:7531-7533
Timestamp: 2025-08-26T11:51:21.817Z
Learning: In WLED PR `#4798`, DedeHai confirmed that certain gamma-related calls in FX.cpp/FX_fcn.cpp/particle systems are intentional for effect-level shaping (e.g., brightness curves, TV sim, Pride 2015 pre-mix), distinct from final output gamma. Do not flag or remove these in future reviews; add comments when feasible to clarify intent.
Learnt from: KrX3D
Repo: wled/WLED PR: 4585
File: usermods/seven_segment_display_reloaded_v2/seven_segment_display_reloaded_v2.cpp:612-617
Timestamp: 2026-02-18T00:41:15.185Z
Learning: In the seven_segment_display_reloaded_v2 usermod (PR `#4585`), the `addToConfig()` method intentionally calls `_updateMQTT()` to publish MQTT state on every config save. This behavior is by design and should not be flagged as an issue.
Learnt from: softhack007
Repo: wled/WLED PR: 5443
File: wled00/FX_fcn.cpp:1277-1277
Timestamp: 2026-03-24T12:10:32.630Z
Learning: In WLED's `WS2812FX::service()` (wled00/FX_fcn.cpp), the old condition `|| (doShow && seg.mode == FX_MODE_STATIC)` was an **inclusion** guard — it caused FX_MODE_STATIC to render only when another segment had already set doShow=true. It did NOT skip or protect FX_MODE_STATIC from rendering. The PR `#5443` simplification removes this condition, meaning FX_MODE_STATIC now renders on every `timeToShow` tick uniformly. This is intentional and not a regression. Do not flag FX_MODE_STATIC special-casing as missing in future reviews of this function.
Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2026-02-22T17:17:24.214Z
Learning: In WLED's deserializeState() (wled00/json.cpp), when a playlist is loaded via `loadPlaylist()`, setting `stateChanged = false` immediately after prevents premature LED updates with stale state. This allows the first preset in the playlist to handle the state update with correct values, while still preserving other JSON processing (WiFi config, palette removal, playlist advance flags) that follows in the function.
Learnt from: BobLoeffler68
Repo: wled/WLED PR: 5440
File: usermods/user_fx/user_fx.cpp:1304-1313
Timestamp: 2026-03-25T07:03:35.475Z
Learning: In WLED `mode_dissolveplus` (usermods/user_fx/user_fx.cpp), using `hw_random16(SEGLEN)` to select the survivor pixel index is correct and safe for this 1D-only effect. The 0xFFFF unmapped-entry concern from the physical bus mapping does not apply to 1D segments because virtual indices 0..SEGLEN-1 always map to valid physical LEDs without gaps. Do not flag this as a bug in future reviews of 1D effects.
Learnt from: blazoncek
Repo: wled/WLED PR: 5140
File: wled00/data/settings_time.htm:66-76
Timestamp: 2025-12-01T07:01:16.949Z
Learning: In WLED PR `#5134`, the fix for macros being initialized with the enable bit set only handles new configurations, not existing ones. If there is a bug in timer/macro handling code (e.g., in settings_time.htm), it must be fixed to work correctly for existing configurations as well.
Learnt from: willmmiles
Repo: wled/WLED PR: 5462
File: wled00/json.cpp:1189-1198
Timestamp: 2026-03-30T15:32:08.847Z
Learning: In WLED's `respondModeData()` (wled00/json.cpp), the 256-character `lineBuffer` limit for effect descriptor strings (getModeData) is an intentional constraint that matches the same 256-char limit used ~6 other places in the codebase. It is not new to this PR and should not be flagged as a regression. Lifting it requires a future refactor (better type system or dynamic buffer).
Learnt from: KrX3D
Repo: wled/WLED PR: 4585
File: usermods/seven_segment_display_reloaded_v2/seven_segment_display_reloaded_v2.cpp:3-5
Timestamp: 2026-02-18T00:11:02.403Z
Learning: In PR `#4585`, the seven_segment_display_reloaded_v2 usermod intentionally uses `#error` to require MQTT (`#ifdef WLED_DISABLE_MQTT `#error` "This user mod requires MQTT to be enabled." `#endif``) because the integration flow has not been tested or validated without MQTT, and failing fast at compile time is preferred over allowing potentially broken builds until a full MQTT-optional refactor is completed.
Learnt from: CR
Repo: wled/WLED PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-04-08T19:07:13.970Z
Learning: Every pull request should include a clear description of what changed and why, with screenshots for new features and links to related issues
softhack007
left a comment
There was a problem hiding this comment.
@ampledashes please address/ clarify the points raised by reviewers.
Summary is here
Added lastPreset variable to track the last preset used for auto-saving functionality so that the last preset active will be called upon reboot if the user has those settings configured