Implement BLE OTA
This commit is contained in:
+32
@@ -1015,3 +1015,35 @@ if (!navigator.bluetooth) {
|
||||
} else {
|
||||
log('Web Bluetooth ready. Click CONNECT to pair your IMU Mouse.','info');
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// OTA firmware update
|
||||
//
|
||||
// The OTAFIX bootloader uses Nordic Legacy DFU (service 00001530-...) which is
|
||||
// blocklisted in Chrome's Web Bluetooth implementation. Browser-side upload is
|
||||
// therefore not possible without special flags or a native app wrapper.
|
||||
//
|
||||
// What the UI does instead:
|
||||
// • "Enter DFU Mode" sends command 0x02 via BLE → device reboots as XIAO_DFU
|
||||
// • User then uploads firmware_dfu.zip via nRF Connect (mobile or desktop)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
function otaLog(msg, type = 'info') {
|
||||
log('[OTA] ' + msg, type);
|
||||
const el = document.getElementById('otaStatus');
|
||||
if (el) { el.textContent = msg; el.className = 'ota-status' + (type !== 'info' ? ' ota-' + type : ''); }
|
||||
}
|
||||
|
||||
// Send command 0x02 → firmware reboots into XIAO_DFU bootloader mode.
|
||||
// User then uploads firmware_dfu.zip via nRF Connect.
|
||||
async function sendOTATrigger() {
|
||||
if (!chars.command) { otaLog('Not connected', 'err'); return; }
|
||||
document.getElementById('btnOTA').disabled = true;
|
||||
try {
|
||||
await chars.command.writeValueWithResponse(new Uint8Array([0x02]));
|
||||
otaLog('Device rebooting into DFU mode — connect to XIAO_DFU in nRF Connect', 'ok');
|
||||
} catch (e) {
|
||||
otaLog('Failed: ' + e.message, 'err');
|
||||
document.getElementById('btnOTA').disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,6 +192,29 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="section-label" style="margin-top:8px">Firmware Update (OTA)</div>
|
||||
<div class="card ota-card" id="otaCard">
|
||||
<div class="ota-notice">
|
||||
<div class="ota-notice-icon">⚠</div>
|
||||
<div>
|
||||
<div class="ota-notice-title">Browser OTA not available</div>
|
||||
<div class="ota-notice-body">Chrome blocks the Nordic Legacy DFU service UUID used by this bootloader. Use <strong>nRF Connect</strong> (mobile or desktop) to upload firmware instead.</div>
|
||||
</div>
|
||||
</div>
|
||||
<ol class="ota-steps">
|
||||
<li>Build firmware: <code>pio run</code> → produces <code>firmware_dfu.zip</code></li>
|
||||
<li>Click <strong>Enter DFU Mode</strong> below — device reboots as <em>XIAO_DFU</em></li>
|
||||
<li>Open nRF Connect → connect to <em>XIAO_DFU</em> → DFU → select <code>firmware_dfu.zip</code></li>
|
||||
</ol>
|
||||
<div class="ota-btn-row" style="grid-template-columns:1fr">
|
||||
<button class="cmd-btn ota-trigger" id="btnOTA" onclick="sendOTATrigger()" disabled>
|
||||
<span class="cmd-icon">⟳</span><span>Enter DFU Mode</span>
|
||||
<span class="cmd-desc">Reboots device into XIAO_DFU so nRF Connect can upload firmware.</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="ota-status-row"><div class="ota-status" id="otaStatus"></div></div>
|
||||
</div>
|
||||
|
||||
<div class="section-label" style="margin-top:8px">Event Log</div>
|
||||
<div class="console" id="console"></div>
|
||||
|
||||
@@ -249,6 +272,7 @@
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
||||
<div class="overlay" id="overlay">
|
||||
<div class="modal">
|
||||
<h3>⚠ Factory Reset</h3>
|
||||
|
||||
@@ -308,6 +308,7 @@
|
||||
.no-ble p { font-size:13px; color:var(--label); line-height:1.8; }
|
||||
|
||||
body.disconnected .card { opacity:0.45; pointer-events:none; transition:opacity 0.3s; }
|
||||
body.disconnected .card.ota-card { opacity:1; pointer-events:auto; } /* OTA works when disconnected too */
|
||||
body.disconnected .cmd-grid { opacity:0.45; pointer-events:none; transition:opacity 0.3s; }
|
||||
|
||||
.tap-key-row { display:flex; align-items:center; gap:10px; padding-top:12px; flex-wrap:wrap; }
|
||||
@@ -325,3 +326,21 @@
|
||||
.tap-flash.right { background:radial-gradient(circle at center, var(--tap-right) 0%, transparent 70%); }
|
||||
.tap-flash.show { opacity:1; }
|
||||
.viz-wrap { position:relative; }
|
||||
|
||||
/* ── OTA Firmware Update ── */
|
||||
.ota-card { display:flex; flex-direction:column; gap:14px; }
|
||||
.ota-notice { display:flex; gap:12px; align-items:flex-start; padding:10px 12px; background:color-mix(in srgb, var(--warn) 8%, var(--bg)); border-left:3px solid var(--warn); }
|
||||
.ota-notice-icon { font-size:16px; color:var(--warn); flex-shrink:0; line-height:1.4; }
|
||||
.ota-notice-title { font-family:var(--sans); font-size:11px; font-weight:700; color:var(--warn); letter-spacing:0.08em; text-transform:uppercase; margin-bottom:4px; }
|
||||
.ota-notice-body { font-family:var(--mono); font-size:10px; color:var(--label); line-height:1.6; }
|
||||
.ota-steps { font-family:var(--mono); font-size:10px; color:var(--label); line-height:1.9; margin:0; padding-left:18px; }
|
||||
.ota-steps code { color:var(--text); }
|
||||
.ota-steps strong { color:var(--text); }
|
||||
.ota-steps em { color:var(--accent); font-style:normal; }
|
||||
.ota-btn-row { display:grid; gap:8px; }
|
||||
.ota-status-row { min-height:14px; }
|
||||
.ota-status { font-family:var(--mono); font-size:10px; color:var(--label); }
|
||||
.ota-status.ota-ok { color:var(--ok); }
|
||||
.ota-status.ota-err { color:var(--accent2); }
|
||||
.cmd-btn.ota-trigger::before { background:var(--accent); }
|
||||
.cmd-btn.ota-trigger:hover { border-color:var(--accent); }
|
||||
|
||||
Reference in New Issue
Block a user