Inject commit hash to FW, show on frontend

This commit is contained in:
2026-03-01 21:38:51 +01:00
parent a3b5425d0f
commit 8cb92f9914
8 changed files with 110 additions and 2 deletions
+47 -1
View File
@@ -2,10 +2,11 @@
// v3.3: 4 characteristics instead of 10
const SVC_UUID = '00001234-0000-1000-8000-00805f9b34fb';
const CHR = {
configBlob: '00001235-0000-1000-8000-00805f9b34fb', // ConfigBlob R/W 16 bytes
configBlob: '00001235-0000-1000-8000-00805f9b34fb', // ConfigBlob R/W 20 bytes
command: '00001236-0000-1000-8000-00805f9b34fb', // Command W 1 byte
telemetry: '00001237-0000-1000-8000-00805f9b34fb', // Telemetry R/N 24 bytes
imuStream: '00001238-0000-1000-8000-00805f9b34fb', // ImuStream N 14 bytes
gitHash: '00001239-0000-1000-8000-00805f9b34fb', // GitHash R 8 bytes
};
// Local shadow of the current config (kept in sync with device)
@@ -113,10 +114,14 @@ async function discoverServices() {
chars.command = await svc.getCharacteristic(CHR.command);
chars.telemetry = await svc.getCharacteristic(CHR.telemetry);
chars.imuStream = await svc.getCharacteristic(CHR.imuStream);
chars.gitHash = await svc.getCharacteristic(CHR.gitHash);
// Read config blob and populate UI
await readConfigBlob();
// Read firmware git hash and check against web build hash
await checkHashMatch();
// Telemetry notify (1 Hz) — also carries chargeStatus
chars.telemetry.addEventListener('characteristicvaluechanged', e => parseTelemetry(e.target.value));
await chars.telemetry.startNotifications();
@@ -149,6 +154,46 @@ async function discoverServices() {
} catch(e) { log('Battery service unavailable','warn'); }
}
// ── Firmware / web hash mismatch banner ──────────────────────────────────────
async function checkHashMatch() {
const banner = document.getElementById('hashMismatchBanner');
if (!chars.gitHash) return;
let fwHash = 'unknown';
try {
const dv = await chars.gitHash.readValue();
const bytes = new Uint8Array(dv.buffer, dv.byteOffset, dv.byteLength);
// Find NUL terminator or use full length
let end = bytes.indexOf(0);
if (end === -1) end = bytes.length;
fwHash = new TextDecoder().decode(bytes.subarray(0, end));
} catch(e) { log(`Hash read failed: ${e.message}`, 'warn'); }
// FIRMWARE_BUILD_HASH comes from web/version.js (written by scripts/git_hash.py at build time)
const webHash = (typeof FIRMWARE_BUILD_HASH !== 'undefined') ? FIRMWARE_BUILD_HASH : 'unknown';
log(`Firmware hash: ${fwHash} · Web hash: ${webHash}`, fwHash === webHash ? 'ok' : 'warn');
if (fwHash === 'unknown' || webHash === 'unknown' || fwHash === webHash) {
banner.style.display = 'none';
return;
}
banner.style.cssText = [
'display:flex', 'align-items:center', 'justify-content:center', 'gap:12px',
'background:#7a2020', 'color:#ffd0d0', 'font-family:var(--mono)',
'font-size:11px', 'padding:6px 16px', 'border-bottom:1px solid #c04040',
'position:sticky', 'top:0', 'z-index:100',
].join(';');
banner.innerHTML =
`<span style="font-size:14px">⚠</span>` +
`<span>FIRMWARE / WEB MISMATCH — ` +
`firmware <b>${fwHash}</b> · web <b>${webHash}</b> — ` +
`flash firmware or reload the page after a <code>pio run</code></span>` +
`<button onclick="document.getElementById('hashMismatchBanner').style.display='none'" ` +
`style="margin-left:8px;background:none;border:1px solid #c04040;color:#ffd0d0;` +
`cursor:pointer;padding:2px 8px;font-family:var(--mono);font-size:10px">✕</button>`;
}
// ── ConfigBlob read / write ──────────────────────────────────────────────────
// ConfigBlob layout (20 bytes LE):
// float sensitivity [0], float deadZone [4], float accelStrength [8]
@@ -419,6 +464,7 @@ function onDisconnected() {
document.getElementById('badgeCharging').classList.remove('show');
document.getElementById('badgeFull').classList.remove('show');
imuSubscribed = false; vizPaused = true; vizUpdateIndicator(); streamDiagReset();
document.getElementById('hashMismatchBanner').style.display = 'none';
clearTelemetry();
if (!userDisconnected && document.getElementById('autoReconnect').checked && savedDevice) {
log('Auto-reconnecting…','info');
+3
View File
@@ -7,9 +7,12 @@
<link href="https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=Barlow+Condensed:wght@300;400;600;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css">
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/build/three.min.js"></script>
<script src="version.js"></script>
</head>
<body class="disconnected">
<div id="hashMismatchBanner" style="display:none"></div>
<header>
<div>
<div class="logo">IMU<span>·</span>Mouse</div>
+2
View File
@@ -0,0 +1,2 @@
// Auto-generated by scripts/git_hash.py — do not edit
const FIRMWARE_BUILD_HASH = 'a3b5425';