diff --git a/dashboard/js/onboard.js b/dashboard/js/onboard.js index a9ad467..c92fcad 100644 --- a/dashboard/js/onboard.js +++ b/dashboard/js/onboard.js @@ -30,8 +30,8 @@ var STEPS = [ { id: 'browser_check', label: 'Browser' }, { id: 'connect_device', label: 'Connect' }, - { id: 'flash_firmware', label: 'Flash' }, { id: 'provision_wifi', label: 'WiFi' }, + { id: 'flash_firmware', label: 'Flash' }, { id: 'detect_node', label: 'Detect' }, { id: 'calibrate', label: 'Calibrate' }, { id: 'placement', label: 'Position' }, @@ -516,7 +516,7 @@ appendLog('log', ['Connected: ' + chip]); setProgress(12); - // 4. Flash + // 4. Flash (progress 12% → 80%) setStatus('Erasing and flashing...'); document.getElementById('flash-log-details').open = true; @@ -529,7 +529,7 @@ compress: true, reportProgress: function (fileIndex, written, total) { if (cancelled) { return; } - var pct = 12 + Math.round(written / total * 83); + var pct = 12 + Math.round(written / total * 68); setProgress(pct); setStatus('Flashing... ' + Math.round(written / total * 100) + '%'); } @@ -537,13 +537,32 @@ await transport.disconnect(); transport = null; + setProgress(80); + appendLog('log', ['Flash complete — device rebooting']); if (cancelled) { return; } + // 5. Provision (progress 80% → 100%) + // Device reboots after flash and opens its provisioning window. + // Send WiFi + mothership config over serial immediately. + setStatus('Configuring device...'); + + var provLog = function (level, msg) { + appendLog(level, [msg]); + }; + var mac = await doProvision(provLog, setStatus, setProgress); + + if (cancelled) { return; } setProgress(100); - setStatus('✓ Firmware flashed successfully!', '#a5d6a7'); - appendLog('log', ['Flash complete']); + setStatus('✓ Device configured!', '#a5d6a7'); + appendLog('log', ['Provisioning complete — MAC: ' + (mac || 'unknown')]); restoreConsole(); + // Snapshot existing nodes before advancing so detect step knows what's new + try { + var nodesResp = await fetch(CONFIG.nodesEndpoint); + var nodes = await nodesResp.json(); + state.knownMACs = (nodes || []).map(function (n) { return n.mac; }); + } catch (_) {} saveState(); setTimeout(function () { goToStep(state.currentStepIndex + 1); }, 1200); @@ -576,6 +595,59 @@ } } + // Runs after firmware flash: fetches provisioning payload from server (or + // builds client-side fallback) and sends it over serial while the device's + // boot provisioning window is open. + async function doProvision(provLog, setStatus, setProgress) { + var ssid = state.wifiSSID; + var pass = state.wifiPass; + var msHost = state.mothershipHost; + var msPort = state.mothershipPort; + + // Fetch server payload (generates node_id + token). + // Race it against a 5s timeout so we don't stall the provisioning window. + var payload = null; + try { + provLog('log', 'POST ' + CONFIG.provisioningEndpoint); + var fetchPromise = fetch(CONFIG.provisioningEndpoint, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ wifi_ssid: ssid, wifi_pass: pass }), + }); + var timeoutPromise = new Promise(function (_, reject) { + setTimeout(function () { reject(new Error('timeout')); }, 5000); + }); + var resp = await Promise.race([fetchPromise, timeoutPromise]); + if (!resp.ok) { throw new Error('HTTP ' + resp.status); } + payload = await resp.json(); + if (msHost) payload.ms_mdns = msHost; + if (msPort) payload.ms_port = msPort; + provLog('log', 'Server payload: node_id=' + (payload.node_id || '(none)')); + } catch (err) { + provLog('warn', 'Mothership unreachable (' + (err.message || err) + '), using client-side payload'); + payload = { + wifi_ssid: ssid, + wifi_pass: pass, + node_id: crypto.randomUUID ? crypto.randomUUID() : + 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + var r = Math.random() * 16 | 0; + return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); + }), + node_token: '', + ms_mdns: msHost || 'spaxel-mothership.local', + ms_port: msPort, + debug: false, + }; + } + setProgress(85); + + var addProvLog = function (level, msg) { provLog(level, msg); }; + var setProvStatus = function (msg) { setStatus(msg); }; + var mac = await sendPayloadOverSerial(payload, addProvLog, setProvStatus); + setProgress(95); + return mac; + } + doFlash(); return { cleanup: function () { cancelled = true; restoreConsole(); } }; } @@ -584,7 +656,7 @@ contentEl.innerHTML = '
Enter your WiFi credentials. The ESP32-S3 needs to connect to the same network as this computer.
' + + 'Enter your WiFi credentials. These will be flashed to the device in the next step.
' + '