From 5e3ed909ee1ab6eb20c392fd927377d165ce3762 Mon Sep 17 00:00:00 2001 From: Justin Bunton <130115204+Bunton33@users.noreply.github.com> Date: Sun, 25 Oct 2026 12:42:55 -0400 Subject: [PATCH 1/3] M-1: honour a no-WiFi owner's launch preset instead of always showing the setup signpost b8 applied preset 9 (dog, then setup card) at every boot while WiFi was unconfigured, whatever launch preset the owner had chosen. The choice was saved and shown in the UI but never ran, so owners using the M-1 without WiFi could not pick what it starts with. The signpost now plays only while WiFi is unconfigured AND the launch preset is still the factory one AND the owner has not chosen one AND presets.json has preset 9. A new persisted flag (cfg def.psc) records the choice: saving Config > LED & Hardware while no WiFi is configured, or ticking "Apply at boot" on a different preset (re-saving the pre-ticked dog preset does not count). No filesystem change: b8 units get the fix by OTA, and an owner who already set a launch preset other than the factory one gets it immediately. A wiped filesystem or a WLED-MM upgrade without preset 9 now runs its own launch preset instead of a missing preset 9. The signpost is compiled in only by [env:apollo_m1] (-D FACTORY_SIGNPOST_PRESET=9 -D FACTORY_BOOT_PRESET=4); other envs of the fork boot like upstream. apollo/boot-signpost-test.js (npm test) reads the condition from wled.cpp and checks the boot cases and both triggers. Co-Authored-By: Claude Opus 5.5 --- apollo/boot-signpost-test.js | 116 +++++++++++++++++++++++++++++++++++ platformio.ini | 1 + wled00/cfg.cpp | 6 ++ wled00/const.h | 4 -- wled00/presets.cpp | 3 + wled00/set.cpp | 3 + wled00/wled.cpp | 23 ++++--- wled00/wled.h | 3 + 8 files changed, 148 insertions(+), 11 deletions(-) create mode 100644 apollo/boot-signpost-test.js diff --git a/apollo/boot-signpost-test.js b/apollo/boot-signpost-test.js new file mode 100644 index 0000000000..51f8127543 --- /dev/null +++ b/apollo/boot-signpost-test.js @@ -0,0 +1,116 @@ +// AI: below section was generated by an AI +// Host-side check of the Apollo M-1 boot signpost in wled00/wled.cpp beginStrip() (DECISIONS D23/D24/D27). +// Runs with `npm test` (node --test). It reads the signpost condition from the C++ source and the signpost +// presets from [env:apollo_m1], evaluates the condition for each boot case, and asserts: +// - a factory-fresh unit with no WiFi (factory def.ps, no def.psc) still boots the signpost playlist, +// - a no-WiFi owner's launch preset is honoured once chosen (Apollo Dog included) or when it is not the factory one, +// - WiFi-configured units boot their own preset exactly as upstream, +// - the signpost is compiled in only where [env:apollo_m1] enables it, and the values match apollo/fs, +// - every code path that writes bootPreset from the UI also sets bootPresetChosen, and cfg.cpp persists it. +'use strict'; +const test = require('node:test'); +const assert = require('node:assert'); +const fs = require('fs'); +const path = require('path'); + +const root = path.join(__dirname, '..'); +const read = f => fs.readFileSync(path.join(root, f), 'utf8'); +const stripIniComments = s => s.split('\n').map(l => l.replace(/;.*$/, '')).join('\n'); + +// [env:apollo_m1] build flags (section runs to the next [section] or end of file) +const ini = read('platformio.ini'); +const envStart = ini.indexOf('[env:apollo_m1]'); +assert.ok(envStart >= 0, '[env:apollo_m1] not found in platformio.ini'); +const envEnd = ini.indexOf('\n[', envStart + 1); +const envM1 = stripIniComments(ini.slice(envStart, envEnd < 0 ? undefined : envEnd)); +const flag = name => { const m = envM1.match(new RegExp(`-D\\s*${name}=(\\d+)`)); return m ? +m[1] : undefined; }; +const SIGNPOST = flag('FACTORY_SIGNPOST_PRESET'); +const FACTORY_BOOT = flag('FACTORY_BOOT_PRESET'); + +// beginStrip() body and the signpost condition inside its #ifdef +const wledCpp = read('wled00/wled.cpp'); +const bsStart = wledCpp.indexOf('void WLED::beginStrip()'); +const beginStrip = wledCpp.slice(bsStart, wledCpp.indexOf('\nvoid WLED::', bsStart + 1)); +const gated = beginStrip.match(/#ifdef FACTORY_SIGNPOST_PRESET([\s\S]*?)#endif/); +// if () { String x; if (getPresetName(FACTORY_SIGNPOST_PRESET, x)) bootPs = FACTORY_SIGNPOST_PRESET; } +const condMatch = gated && gated[1].match(/if \(([^;{}]*?)\)\s*\{\s*String (\w+);\s*if \(getPresetName\(FACTORY_SIGNPOST_PRESET, \2\)\)\s*bootPs\s*=\s*FACTORY_SIGNPOST_PRESET;\s*\}/); + +// boot decision as compiled for the M-1: returns the preset applied, or 'DEFAULT_COLOR' when none. +// signpostExists models getPresetName(9): false when presets.json has no named preset 9 (e.g. a WLED-MM upgrade). +function bootM1(wifiConfigured, chosen, bootPreset, signpostExists = true) { + const cond = condMatch[1]; + const ids = new Set(cond.match(/[A-Za-z_]\w*/g)); + const allowed = ['WLED_WIFI_CONFIGURED', 'bootPresetChosen', 'bootPreset', 'FACTORY_BOOT_PRESET', 'FACTORY_SIGNPOST_PRESET']; + assert.ok([...ids].every(id => allowed.includes(id)), `unexpected identifier in signpost condition: ${cond}`); + assert.match(cond, /^[\s!&|=()\w]+$/, `signpost condition is not a plain boolean expression: ${cond}`); + const fn = new Function(...allowed, `return (${cond});`); + const ps = (fn(wifiConfigured, chosen, bootPreset, FACTORY_BOOT, SIGNPOST) && signpostExists) ? SIGNPOST : bootPreset; + return ps > 0 ? ps : 'DEFAULT_COLOR'; +} + +test('signpost is compiled in only by [env:apollo_m1]', () => { + assert.strictEqual(SIGNPOST, 9, '-D FACTORY_SIGNPOST_PRESET missing from [env:apollo_m1]'); + assert.strictEqual(FACTORY_BOOT, 4, '-D FACTORY_BOOT_PRESET missing from [env:apollo_m1]'); + const elsewhere = stripIniComments(ini.slice(0, envStart)).match(/FACTORY_SIGNPOST_PRESET/g); + assert.strictEqual(elsewhere, null, 'another env defines FACTORY_SIGNPOST_PRESET'); + for (const f of ['wled00/const.h', 'wled00/wled.h']) { + assert.doesNotMatch(read(f), /#define\s+FACTORY_SIGNPOST_PRESET/, `${f} must not define a default`); + } + assert.ok(condMatch, 'signpost condition not found inside #ifdef FACTORY_SIGNPOST_PRESET in beginStrip()'); + assert.match(beginStrip, /byte bootPs = bootPreset;/); + assert.match(beginStrip, /if \(bootPs > 0\) \{\s*applyPreset\(bootPs, CALL_MODE_INIT\);/); + assert.doesNotMatch(beginStrip.replace(gated[0], ''), /FACTORY_SIGNPOST_PRESET|bootPresetChosen/, 'signpost code outside the #ifdef'); +}); + +test('flag values match the factory filesystem', () => { + const cfg = JSON.parse(read('apollo/fs/cfg.json')); + const presets = JSON.parse(read('apollo/fs/presets.json')); + assert.strictEqual(cfg.def.ps, FACTORY_BOOT, 'FACTORY_BOOT_PRESET must equal apollo/fs/cfg.json def.ps'); + assert.strictEqual(cfg.def.psc, undefined, 'factory cfg.json must not mark the boot preset as chosen'); + assert.strictEqual(presets[FACTORY_BOOT].seg[0].n, 'apollo_dog.gif'); + const pl = presets[SIGNPOST].playlist; + assert.ok(pl, `preset ${SIGNPOST} must be the signpost playlist`); + assert.deepStrictEqual(pl.ps, [FACTORY_BOOT]); + assert.strictEqual(presets[pl.end].seg[0].n, 'setup.gif', 'signpost playlist must end on the Setup card'); +}); + +test('boot behaviour per case', () => { + const D = FACTORY_BOOT; + // [wifiConfigured, bootPresetChosen, bootPreset, expected, case] + const cases = [ + [false, false, D, SIGNPOST, 'factory-fresh, no WiFi: dog then setup card'], + [false, false, 3, 3, 'no WiFi, launch preset 3 (also b8 units updated by OTA, no psc key)'], + [false, true, 3, 3, 'no WiFi, owner chose 3'], + [false, true, D, D, 'no WiFi, owner chose Apollo Dog'], + [false, true, 0, 'DEFAULT_COLOR', 'no WiFi, owner chose none'], + [false, true, SIGNPOST, SIGNPOST, 'no WiFi, owner chose First Boot on purpose'], + [false, false, 0, 'DEFAULT_COLOR', 'wiped filesystem: Apollo blue, no missing-preset error'], + [false, false, 23, 23, 'WLED-MM upgrade, no WiFi, own boot preset'], + [true, false, D, D, 'WiFi configured: factory dog (D24 self-correction)'], + [true, false, 0, 'DEFAULT_COLOR', 'WiFi configured, no boot preset'], + [true, true, 7, 7, 'WiFi configured, owner preset'], + ]; + for (const [wifi, chosen, bp, want, name] of cases) assert.strictEqual(bootM1(wifi, chosen, bp), want, name); + // no named preset 9 in presets.json (WLED-MM upgrade, or the owner deleted it): the owner's preset 4 runs + assert.strictEqual(bootM1(false, false, D, false), D, 'no preset 9: launch preset 4 runs instead of a missing preset'); +}); + +test('every UI write of bootPreset marks it chosen, and cfg.cpp persists the flag', () => { + for (const f of ['wled00/set.cpp', 'wled00/presets.cpp', 'wled00/json.cpp']) { + const lines = read(f).split('\n'); + lines.forEach((l, i) => { + if (!/\bbootPreset\s*=[^=]/.test(l)) return; + const around = lines.slice(Math.max(0, i - 4), i + 4).join('\n'); // the guard may sit just before or just after the write + assert.match(around, /#ifdef FACTORY_SIGNPOST_PRESET\s*\n[^\n]*bootPresetChosen = true;/, `${f}:${i + 1} writes bootPreset without setting bootPresetChosen`); + }); + } + // LED & Hardware save counts only without WiFi; "Apply at boot" counts only when it picks a different preset + // (the box is pre-ticked on the current boot preset, so re-saving the dog preset must not end the signpost) + assert.match(read('wled00/set.cpp'), /if \(!WLED_WIFI_CONFIGURED\) bootPresetChosen = true;/); + assert.match(read('wled00/presets.cpp'), /if \(\(sObj\[FPSTR\(bootPS\)\] \| bootPreset\) != bootPreset\) bootPresetChosen = true;/); + const cfg = read('wled00/cfg.cpp'); + assert.match(cfg, /CJSON\(bootPreset, def\["ps"\]\);\s*#ifdef FACTORY_SIGNPOST_PRESET\s*CJSON\(bootPresetChosen, def\["psc"\]\);/); + assert.match(cfg, /def\["ps"\] = bootPreset;\s*#ifdef FACTORY_SIGNPOST_PRESET\s*def\["psc"\] = bootPresetChosen;/); + assert.match(read('wled00/wled.h'), /#ifdef FACTORY_SIGNPOST_PRESET\s*WLED_GLOBAL bool bootPresetChosen _INIT\(false\);/); +}); +// AI: end diff --git a/platformio.ini b/platformio.ini index 5ad2a4ee39..d6d5f64425 100644 --- a/platformio.ini +++ b/platformio.ini @@ -916,6 +916,7 @@ build_flags = ${env:esp32s3dev_16MB_opi_hub75.build_flags} -D WLED_MDNS_PREFIX='"apollo-led-matrix"' ;; factory boot: one 64x64 half-scan panel as a 2D matrix, brightness limiter off -D DEFAULT_COLOR=0x4379AA ;; Apollo blue welcome color (needs the FX.h ifndef guard) + -D FACTORY_SIGNPOST_PRESET=9 -D FACTORY_BOOT_PRESET=4 ;; setup signpost (D24/D27): apollo/fs presets.json 9 and cfg.json def.ps; other envs compile it out -D DATA_PINS=64,64,1,1,1 ;; HUB75 slots = panelW,panelH,chain,rows,cols (not GPIOs) -D MAX_LEDS=16388 ;; 4-panel chain is EXACTLY 16384 and the driver guard is >=; +4 pad mirrors WLED-MM (QA D10 finding) -D MAX_LED_MEMORY=262144 ;; 4-panel chain estimate is 180224B; stock 192K cap leaves 17K margin before the bus is silently replaced by a placeholder = dark panels (QA D10) diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index fe0d5d0792..be836cff28 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -590,6 +590,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { JsonObject def = doc["def"]; CJSON(bootPreset, def["ps"]); + #ifdef FACTORY_SIGNPOST_PRESET + CJSON(bootPresetChosen, def["psc"]); // absent in the factory cfg.json and on b8 units: not chosen + #endif CJSON(turnOnAtBoot, def["on"]); // true CJSON(briS, def["bri"]); // 128 @@ -1127,6 +1130,9 @@ void serializeConfig(JsonObject root) { JsonObject def = root.createNestedObject("def"); def["ps"] = bootPreset; + #ifdef FACTORY_SIGNPOST_PRESET + def["psc"] = bootPresetChosen; + #endif def["on"] = turnOnAtBoot; def["bri"] = briS; diff --git a/wled00/const.h b/wled00/const.h index 25a787e4d0..f39e2a20eb 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -457,10 +457,6 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit"); #define SEG_CAPABILITY_W 0x02 #define SEG_CAPABILITY_CCT 0x04 -#ifndef FACTORY_SIGNPOST_PRESET -#define FACTORY_SIGNPOST_PRESET 9 // preset applied at boot while WiFi is unconfigured (setup signpost); no-op if the preset does not exist -#endif - // WLED Error modes #define ERR_NONE 0 // All good :) #define ERR_DENIED 1 // Permission denied diff --git a/wled00/presets.cpp b/wled00/presets.cpp index 9023baf344..42dcc1e6e9 100644 --- a/wled00/presets.cpp +++ b/wled00/presets.cpp @@ -237,6 +237,9 @@ void savePreset(byte index, const char* pname, JsonObject sObj) const char *bootPS = PSTR("bootps"); if (!sObj[FPSTR(bootPS)].isNull()) { + #ifdef FACTORY_SIGNPOST_PRESET + if ((sObj[FPSTR(bootPS)] | bootPreset) != bootPreset) bootPresetChosen = true; // a newly ticked "Apply at boot" stops the setup signpost; re-saving the pre-ticked boot preset does not + #endif bootPreset = sObj[FPSTR(bootPS)] | bootPreset; sObj.remove(FPSTR(bootPS)); configNeedsWrite = true; diff --git a/wled00/set.cpp b/wled00/set.cpp index fb516ac7d6..a5b7d486ae 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -374,6 +374,9 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) turnOnAtBoot = request->hasArg(F("BO")); t = request->arg(F("BP")).toInt(); if (t <= 250) bootPreset = t; + #ifdef FACTORY_SIGNPOST_PRESET + if (!WLED_WIFI_CONFIGURED) bootPresetChosen = true; // saving LED & Hardware without WiFi confirms "Apply preset N at boot": the setup signpost stops + #endif gammaCorrectBri = request->hasArg(F("GB")); gammaCorrectCol = request->hasArg(F("GC")); gammaCorrectVal = request->arg(F("GV")).toFloat(); diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 499da38e66..ac613118d0 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -626,14 +626,23 @@ void WLED::beginStrip() else briLast = briS; // go to startup brightness (set in UI) when turning on (can be overruled by a preset) colorUpdated(CALL_MODE_INIT); // set bootup brightness immediately, do not send notification (brightness is also set for preset if used, useful for swipe etc.) - if (!WLED_WIFI_CONFIGURED && bootPreset != FACTORY_SIGNPOST_PRESET) { - // WiFi not configured yet: show the setup signpost tour (logo / AP address / QR code) - // instead of the regular boot preset; self-corrects to bootPreset once provisioned. - // (cannot use showWelcomePage: it is computed after beginStrip() in setup()) - applyPreset(FACTORY_SIGNPOST_PRESET, CALL_MODE_INIT); + byte bootPs = bootPreset; + #ifdef FACTORY_SIGNPOST_PRESET + // AI: below section was generated by an AI + // Apollo M-1 setup signpost (DECISIONS D23/D24/D27), enabled by [env:apollo_m1] only. With no WiFi configured and the + // launch preset still the factory one (cfg.json def.ps), boot plays the signpost playlist (dog, then the setup card) + // instead. It stops once WiFi is configured or the owner picks a launch preset (bootPresetChosen), so a no-WiFi + // owner's choice is honoured, Apollo Dog included. A wiped filesystem (bootPreset 0), a WLED-MM upgrade with its own + // boot preset, or a filesystem without preset 9 takes the normal path. The preset-9 lookup is one presets.json read, + // done only on unprovisioned units. (cannot use showWelcomePage: it is computed after beginStrip() in setup()) + if (!WLED_WIFI_CONFIGURED && !bootPresetChosen && bootPreset == FACTORY_BOOT_PRESET) { + String spName; + if (getPresetName(FACTORY_SIGNPOST_PRESET, spName)) bootPs = FACTORY_SIGNPOST_PRESET; } - else if (bootPreset > 0) { - applyPreset(bootPreset, CALL_MODE_INIT); + // AI: end + #endif + if (bootPs > 0) { + applyPreset(bootPs, CALL_MODE_INIT); } else { // set color to warm welcoming orange (aka DEFAULT_COLOR) if no preset loaded (will fade to this color once turned on) diff --git a/wled00/wled.h b/wled00/wled.h index 399b9ffa70..9af709b7ac 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -395,6 +395,9 @@ WLED_GLOBAL uint8_t txPower _INIT(WIFI_POWER_19_5dBm); // LED CONFIG WLED_GLOBAL bool turnOnAtBoot _INIT(true); // turn on LEDs at power-up WLED_GLOBAL byte bootPreset _INIT(0); // save preset to load after power-up +#ifdef FACTORY_SIGNPOST_PRESET +WLED_GLOBAL bool bootPresetChosen _INIT(false); // owner picked the boot preset (LED settings or "Apply at boot"): no setup signpost at boot +#endif //if true, a segment per bus will be created on boot and LED settings save //if false, only one segment spanning the total LEDs is created, From 0becf9a5297b8a320ed160567c95072582769aa4 Mon Sep 17 00:00:00 2001 From: Justin Bunton <130115204+Bunton33@users.noreply.github.com> Date: Sun, 25 Oct 2026 12:42:55 -0400 Subject: [PATCH 2/3] Docs: D27, QA check and no-WiFi page for the launch preset fix D27 records the bug, the fix and the trade-off that needs sign-off. QA gains a D27 section run on a separate unit (the b8 gate never set a launch preset without WiFi). The no-WiFi wiki draft explains how to choose what the M-1 starts with, uses the real menu name (Config > LED & Hardware) and fixes the stale "Apollo M-1-xxxxxx" hotspot name. Co-Authored-By: Claude Opus 5.5 --- DECISIONS.md | 28 ++++++++++++++++++++++++++++ QA_CHECKLIST.md | 27 ++++++++++++++++++++++++--- apollo/README.md | 5 +++-- apollo/wiki-rewrites/no-wifi-mode.md | 28 ++++++++++++++++++++-------- 4 files changed, 75 insertions(+), 13 deletions(-) diff --git a/DECISIONS.md b/DECISIONS.md index 15accaf2d7..77f87ec788 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -311,3 +311,31 @@ its unique suffix (apollo-led-matrix-xxxxxx); only the hotspot name changed. FIRMWARE change: b8 app no longer byte-identical to b7, virgin boot + battery re-run before the walkthrough; the SSID fix reaches existing units via OTA, unlike the FS card. + +### D27. b9 (2026-10-25): a no-WiFi owner's launch preset wins over the setup signpost +BUG in b8 (reported by Justin): with no WiFi configured, beginStrip() applied +preset 9 (dog, then setup card) instead of ANY launch preset, so an AP-only +owner's launch preset was saved and shown in the UI but never ran at boot. +That contradicts D20 (full control with no network). No earlier decision or +QA row covered it; the D13 gate never set a launch preset on an AP-only unit. +FIX: the signpost plays only while WiFi is unconfigured AND the launch +preset is still the factory one (cfg.json def.ps 4) AND the owner has not +chosen one AND presets.json has a named preset 9. "Chosen" is a new +persisted flag (cfg def.psc). It is set when Config > LED & Hardware is +saved while no WiFi is configured (the "Apply preset N at boot" field under +Power up), or when "Apply at boot" is ticked on a DIFFERENT preset in the +preset editor (that box is pre-ticked on the current launch preset, so just +re-saving the dog preset does not count). Choosing the dog itself therefore +works through LED & Hardware. Compiled in only by [env:apollo_m1] +(-D FACTORY_SIGNPOST_PRESET=9 -D FACTORY_BOOT_PRESET=4); other envs of the +fork boot like upstream. No apollo/fs change, so it reaches b8 units by OTA: +an owner who already set a launch preset other than 4 gets it at once. +Also fixes a wiped filesystem (factory reset) and a WLED-MM upgrade without +preset 9, which now run their launch preset (0 = plain Apollo blue) instead +of applying a missing preset 9. +TRADEOFF, NEEDS PRODUCT SIGN-OFF: saving LED & Hardware without WiFi for +ANY reason (for example the HUB75 color order from PR #8) counts as choosing +the launch preset shown there, so the boot signpost stops. The card stays +available on demand as preset 5 "Setup". There is no UI way back to the +signpost short of a full install; do not tell owners to use launch preset +9 for it, because 9 keeps showing the hotspot card after WiFi is set up. diff --git a/QA_CHECKLIST.md b/QA_CHECKLIST.md index 7307b6446f..2fc972ad25 100644 --- a/QA_CHECKLIST.md +++ b/QA_CHECKLIST.md @@ -270,9 +270,10 @@ unit. This is the D20 acceptance: AP mode is a first-class destination. STATIC setup card (D24): STEP 1 JOIN APOLLO WIFI HOTSPOT / 21px QR / STEP 2 SCAN OR OPEN BROWSER TO 4.3.2.1, at brightness 220, holding indefinitely. NO cycling, no page turns. -- [ ] Reset or power-cycle: dog then card return (boot preset 9 playlist - self-heals; customer preset list shows 5 "Setup" to recall the card). -- [ ] Phone joins "Apollo M-1-xxxxxx" (open). Accept any "no internet - +- [ ] Reset or power-cycle, no launch preset saved: dog then card return + (boot preset 9 playlist self-heals; customer preset list shows 5 + "Setup" to recall the card). +- [ ] Phone joins "Apollo M-1" (open). Accept any "no internet - stay connected?" prompt. NO captive popup should appear (D22). - [ ] Scan the QR off the panel with the camera app in NORMAL ROOM LIGHT: browser opens http://4.3.2.1 (with mobile data still ON). @@ -305,3 +306,23 @@ PASS ("everything looks great"), with Sound Bars verified as applies-cleanly-idle on this no-mic unit (reactivity row inherited from the 2026-07-15 glass verification on the mic-equipped unit; audio code untouched b7->b8). SHIP-READY RE-ASSERTED for bundle b8. + +## D27. Launch preset without WiFi (b9+; run on a SECOND factory-fresh unit, +## never on the D13 unit: these steps mark the launch preset as chosen, which +## cannot be undone from the UI and would break D13's final provision row) +Phone on the "Apollo M-1" hotspot, 4.3.2.1, no WiFi ever configured. +- [ ] Full install, power-cycle twice without saving any settings: dog then + card both times. +- [ ] Preset editor: open 4 "Apollo Dog" (Apply at boot is pre-ticked), + rename it, save; power-cycle: dog then card STILL return. +- [ ] Config > LED & Hardware > Power up: "Apply preset 3 at boot" > Save; + power-cycle: Sound Bars starts, no dog, no card. /json/cfg shows + def {"ps":3,"psc":true}. +- [ ] Same field = 4 > Save; power-cycle: dog only, no card. +- [ ] Same field = 2 > Save; power-cycle: Apollo Blue preset. +- [ ] Preset editor: tick "Apply at boot" on 1 "Scrolling Text", save; + power-cycle: Scrolling Text plays. +- [ ] Preset 5 "Setup" from the preset list still shows the card on demand. +- [ ] OTA path (third unit, or after a full install of b8): on b8 set the + LED & Hardware launch preset to 3 (dog then card still show: the b8 + bug), OTA M-1_ota.bin b9, power-cycle: Sound Bars, no re-save needed. diff --git a/apollo/README.md b/apollo/README.md index 3bd55106d9..ba0604b0f6 100644 --- a/apollo/README.md +++ b/apollo/README.md @@ -13,8 +13,9 @@ tree itself. Rules for branches, upstream WLED and releases: CLAUDE.md in the re - fs/presets.json - the factory presets: 1 Scrolling Text, 2 Apollo Blue, 3 Sound Bars, 4 Apollo Dog, 5 Setup (the first-boot setup card at brightness 220), and 9 First Boot (a playlist that plays the dog once for 4 s, then holds the Setup - card; applied automatically at boot while WiFi is unconfigured, see - FACTORY_SIGNPOST_PRESET). Historical note: units shipped on WLED-MM carried no + card; applied at boot while WiFi is unconfigured and the owner has not chosen a + launch preset, see FACTORY_SIGNPOST_PRESET in [env:apollo_m1] and DECISIONS D27). + Historical note: units shipped on WLED-MM carried no presets; these first shipped with the 16.0.1 migration. - fs/apollo_dog.gif - the animated first-boot dog (D19). - fs/setup.gif - the static setup card (D24): join instructions, QR code, and the diff --git a/apollo/wiki-rewrites/no-wifi-mode.md b/apollo/wiki-rewrites/no-wifi-mode.md index 8ddd1f4cee..634cd6a318 100644 --- a/apollo/wiki-rewrites/no-wifi-mode.md +++ b/apollo/wiki-rewrites/no-wifi-mode.md @@ -21,10 +21,9 @@ panel shows a setup card and keeps showing it until you are set up. The card has both steps and a QR code, so there is nothing to wait for. 1. **STEP 1 - join the M-1's WiFi.** On your phone or computer, open the - WiFi list and join the network called **Apollo M-1-xxxxxx** (the letters - at the end are unique to your unit). There is no password. If your phone - warns "this network has no internet - stay connected?", choose **stay - connected**. You can leave mobile data on. + WiFi list and join the network called **Apollo M-1**. There is no + password. If your phone warns "this network has no internet - stay + connected?", choose **stay connected**. You can leave mobile data on. 2. **STEP 2 - scan the code on the card.** Point your camera app at the QR code at the bottom of the card and tap the link. Your browser opens the M-1's control page. @@ -38,10 +37,23 @@ ever touching a network. ### Good to know -- The setup card only appears while the M-1 has no WiFi configured. Once - you connect it to your home WiFi, it boots straight to the Apollo dog - instead, and you reach it at its normal network address. You can always - bring the card back from the preset list (preset "Setup"). +- The setup card appears at power-on only while the M-1 has no WiFi + configured and you have not chosen what it should start with. Once you + connect it to your home WiFi, it starts on its launch preset instead (the + Apollo dog, unless you changed it), and you reach it at its normal network + address. You can always bring the card back from the preset list (preset + "Setup"). +- **Staying without WiFi? Choose what it starts with (firmware b9 or + later).** Open **Config > LED & Hardware**, scroll to **Power up**, set + **Apply preset ___ at boot** (4 = Apollo dog, 1 = Scrolling Text, + 2 = Apollo Blue) and press **Save**. Or open a different preset, tick + **Apply at boot**, and save it. From then on it starts on that preset, + without the setup card. +- Saving the **LED & Hardware** page while the M-1 has no WiFi, for any + reason (for example changing the color order), also counts as choosing: + it starts on the preset shown in **Apply preset ___ at boot** and the + setup card no longer appears at power-on. The card is still in the preset + list as "Setup". - The hotspot stays available as a fallback: if the M-1 ever cannot reach your WiFi, it opens its hotspot again and 4.3.2.1 works as above. - Uploading GIFs from the **WLED phone app** does not work (a known bug in From 02317a77b406ae537735795808daec1d14fbb973 Mon Sep 17 00:00:00 2001 From: Justin Bunton <130115204+Bunton33@users.noreply.github.com> Date: Sun, 25 Oct 2026 13:39:25 -0400 Subject: [PATCH 3/3] Docs: D27 trade-off accepted by Justin Saving LED & Hardware without WiFi for any reason ends the boot setup card; accepted 2026-10-25. The narrower rule was rejected because it would stop owners from choosing the Apollo dog. Co-Authored-By: Claude Opus 5.5 --- DECISIONS.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/DECISIONS.md b/DECISIONS.md index 77f87ec788..0be2f24122 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -333,7 +333,9 @@ an owner who already set a launch preset other than 4 gets it at once. Also fixes a wiped filesystem (factory reset) and a WLED-MM upgrade without preset 9, which now run their launch preset (0 = plain Apollo blue) instead of applying a missing preset 9. -TRADEOFF, NEEDS PRODUCT SIGN-OFF: saving LED & Hardware without WiFi for +TRADEOFF, ACCEPTED (Justin, 2026-10-25; the narrower "only when the launch +preset number changes" was rejected because an owner could then never pick +the dog, already the factory value, from that page): saving LED & Hardware without WiFi for ANY reason (for example the HUB75 color order from PR #8) counts as choosing the launch preset shown there, so the boot signpost stops. The card stays available on demand as preset 5 "Setup". There is no UI way back to the