Remove unnecessary comments, clean up code

This commit is contained in:
2026-03-03 21:38:32 +01:00
parent cb433f76c9
commit 8e9a3712ac
16 changed files with 197 additions and 331 deletions
+60 -60
View File
@@ -1,4 +1,4 @@
// ── UUIDs ────────────────────────────────────────────────────────────────────
// UUIDs
// v3.3: 4 characteristics instead of 10
const SVC_UUID = '00001234-0000-1000-8000-00805f9b34fb';
const CHR = {
@@ -25,11 +25,11 @@ let device=null, server=null, chars={}, userDisconnected=false;
let currentChargeStatus=0, currentBattPct=null, currentBattVoltage=null;
let advancedMode = localStorage.getItem('advanced') === 'true';
// ── GATT write queue (prevents "operation already in progress") ───────────────
// GATT write queue (prevents "operation already in progress")
// Serialises all GATT writes. Features:
// • Per-operation 3s timeout hangs don't block the queue forever
// • Max depth of 2 pending ops drops excess writes when device goes silent
// • gattQueueReset() flushes on disconnect so a reconnect starts clean
// • Per-operation 3s timeout - hangs don't block the queue forever
// • Max depth of 2 pending ops - drops excess writes when device goes silent
// • gattQueueReset() flushes on disconnect so a reconnect starts clean
const GATT_TIMEOUT_MS = 3000;
const GATT_MAX_DEPTH = 2;
let _gattQueue = Promise.resolve();
@@ -45,7 +45,7 @@ function _withTimeout(promise, ms) {
function _enqueue(fn) {
if (_gattDepth >= GATT_MAX_DEPTH) {
return Promise.reject(new Error('GATT queue full device unreachable?'));
return Promise.reject(new Error('GATT queue full - device unreachable?'));
}
_gattDepth++;
const p = _gattQueue.then(() => _withTimeout(fn(), GATT_TIMEOUT_MS));
@@ -62,7 +62,7 @@ function gattQueueReset() {
_gattDepth = 0;
}
// ── Logging ──────────────────────────────────────────────────────────────────
// Logging
(function() {
const _methods = { log: '', warn: 'warn', error: 'err' };
for (const [method, type] of Object.entries(_methods)) {
@@ -85,7 +85,7 @@ function log(msg, type='') {
const p2=n=>String(n).padStart(2,'0'), p3=n=>String(n).padStart(3,'0');
function cssVar(n) { return getComputedStyle(document.documentElement).getPropertyValue(n).trim(); }
// ── Connection ───────────────────────────────────────────────────────────────
// Connection
async function doConnect() {
if (!navigator.bluetooth) { log('Web Bluetooth not supported.','err'); return; }
userDisconnected = false;
@@ -131,27 +131,27 @@ async function discoverServices() {
// Read firmware git hash and check against web build hash
await checkHashMatch();
// Telemetry notify (1 Hz) also carries chargeStatus
// Telemetry notify (1 Hz) - also carries chargeStatus
chars.telemetry.addEventListener('characteristicvaluechanged', e => parseTelemetry(e.target.value));
await chars.telemetry.startNotifications();
// Initial read so values show immediately. Also force updateChargeUI() here
// because parseTelemetry() only calls it on a *change*, and currentChargeStatus
// starts at 0 (discharging) so a discharging device would never trigger the
// starts at 0 (discharging) - so a discharging device would never trigger the
// update and ciStatus would stay at '--'.
parseTelemetry(await chars.telemetry.readValue());
updateChargeUI();
// IMU stream subscribed on demand via play button
// IMU stream - subscribed on demand via play button
chars.imuStream.addEventListener('characteristicvaluechanged', e => parseImuStream(e.target.value));
log('Config service ready (4 chars)','ok');
} catch(e) {
log(`Service discovery failed: ${e.message}`,'err');
// Safe mode device might not have config service
if (e.message.includes('not found')) log('Device may be in safe mode basic mouse only','warn');
if (e.message.includes('not found')) log('Device may be in safe mode - basic mouse only','warn');
}
// Battery service (standard always present)
// Battery service (standard - always present)
try {
const bsvc = await server.getPrimaryService('battery_service');
const bch = await bsvc.getCharacteristic('battery_level');
@@ -167,7 +167,7 @@ async function discoverServices() {
} catch(e) { log('Battery service unavailable','warn'); }
}
// ── Firmware / web hash mismatch banner ──────────────────────────────────────
// Firmware / web hash mismatch banner
async function checkHashMatch() {
const banner = document.getElementById('hashMismatchBanner');
if (!chars.gitHash) return;
@@ -199,20 +199,20 @@ async function checkHashMatch() {
].join(';');
banner.innerHTML =
`<span style="font-size:14px">⚠</span>` +
`<span>FIRMWARE / WEB MISMATCH ` +
`firmware <b>${fwHash}</b> · web <b>${webHash}</b> ` +
`<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 read / write
// ConfigBlob layout (25 bytes LE):
// float sensitivity [0], float deadZone [4], float accelStrength [8]
// uint8 curve [12], uint8 axisFlip [13], uint8 chargeMode [14]
// uint8 tapThreshold [15], uint8 tapAction [16], uint8 tapKey [17], uint8 tapMod [18], uint8 tapFreezeEnabled [19]
// float jerkThreshold [20], uint8 featureFlags [24]
// float sensitivity [0], float deadZone [4], float accelStrength [8]
// uint8 curve [12], uint8 axisFlip [13], uint8 chargeMode [14]
// uint8 tapThreshold [15], uint8 tapAction [16], uint8 tapKey [17], uint8 tapMod [18], uint8 tapFreezeEnabled [19]
// float jerkThreshold [20], uint8 featureFlags [24]
async function readConfigBlob() {
if (!chars.configBlob) return;
@@ -238,7 +238,7 @@ async function readConfigBlob() {
if (view.byteLength >= 25) {
config.featureFlags = view.getUint8(24);
} else {
config.featureFlags = FLAG_ALL_DEFAULT; // old firmware assume all on
config.featureFlags = FLAG_ALL_DEFAULT; // old firmware - assume all on
}
if (view.byteLength >= 28) {
config.btnLeftPin = view.getUint8(25);
@@ -248,7 +248,7 @@ async function readConfigBlob() {
config.btnLeftPin = config.btnRightPin = config.btnMiddlePin = 0xFF; // disabled
}
applyConfigToUI();
log(`Config loaded sens=${config.sensitivity.toFixed(0)} dz=${config.deadZone.toFixed(3)} tapThr=${config.tapThreshold}`,'ok');
log(`Config loaded - sens=${config.sensitivity.toFixed(0)} dz=${config.deadZone.toFixed(3)} tapThr=${config.tapThreshold}`,'ok');
} catch(e) { log(`Config read error: ${e.message}`,'err'); }
}
@@ -284,7 +284,7 @@ function applyConfigToUI() {
updatePinDiagram();
}
// ── XIAO pin diagram ──────────────────────────────────────────────────────────
// XIAO pin diagram
function updatePinDiagram() {
const st = getComputedStyle(document.documentElement);
const COL_L = st.getPropertyValue('--ok').trim();
@@ -362,11 +362,11 @@ async function _doWriteConfigBlob() {
try {
await gattWrite(chars.configBlob, buf);
log(`Config written sens=${config.sensitivity.toFixed(0)} tapThr=${config.tapThreshold} tapAction=${config.tapAction}`,'ok');
log(`Config written - sens=${config.sensitivity.toFixed(0)} tapThr=${config.tapThreshold} tapAction=${config.tapAction}`,'ok');
} catch(e) { log(`Config write failed: ${e.message}`,'err'); }
}
// ── Individual control handlers ───────────────────────────────────────────────
// Individual control handlers
// These update the local config shadow then write the full blob
function setCurve(val) {
@@ -397,7 +397,7 @@ function setChargeModeUI(val) {
function onCapTapChange(enabled) {
writeConfigBlob();
log('Tap detection ' + (enabled ? 'enabled' : 'disabled') + ' restart device to apply', 'warn');
log('Tap detection ' + (enabled ? 'enabled' : 'disabled') + ' - restart device to apply', 'warn');
}
function onTapFreezeChange(enabled) {
@@ -430,7 +430,7 @@ function onTapKeyInput() {
async function sendCalibrate() {
if (!chars.command) return;
try { await gattCmd(chars.command, new Uint8Array([0x01])); log('Calibration sent hold still!','warn'); }
try { await gattCmd(chars.command, new Uint8Array([0x01])); log('Calibration sent - hold still!','warn'); }
catch(e) { log(`Calibrate failed: ${e.message}`,'err'); }
}
function confirmReset() { document.getElementById('overlay').classList.add('show'); }
@@ -444,22 +444,22 @@ async function doReset() {
} catch(e) { log(`Reset failed: ${e.message}`,'err'); }
}
// ── Telemetry ────────────────────────────────────────────────────────────────
// TelemetryPacket (28 bytes LE backwards compatible with 24-byte v3.3):
// uint32 uptime [0], uint32 leftClicks [4], uint32 rightClicks [8]
// float temp [12], float biasRms [16]
// uint16 recalCount [20], uint8 chargeStatus [22], uint8 pad [23]
// float battVoltage [24] (new in v3.4, absent on older firmware)
// Telemetry
// TelemetryPacket (28 bytes LE - backwards compatible with 24-byte v3.3):
// uint32 uptime [0], uint32 leftClicks [4], uint32 rightClicks [8]
// float temp [12], float biasRms [16]
// uint16 recalCount [20], uint8 chargeStatus [22], uint8 pad [23]
// float battVoltage [24] (new in v3.4, absent on older firmware)
function parseTelemetry(dv) {
let view;
try {
view = dv instanceof DataView ? new DataView(dv.buffer, dv.byteOffset, dv.byteLength) : new DataView(dv);
} catch(e) { log(`parseTelemetry: DataView wrap failed ${e.message}`,'err'); return; }
} catch(e) { log(`parseTelemetry: DataView wrap failed - ${e.message}`,'err'); return; }
if (view.byteLength < 24) {
const bytes = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
const hex = Array.from(bytes).map(b=>b.toString(16).padStart(2,'0')).join(' ');
log(`TELEM: expected 24-28B, got ${view.byteLength}B MTU too small? raw: ${hex}`,'err');
log(`TELEM: expected 24-28B, got ${view.byteLength}B - MTU too small? raw: ${hex}`,'err');
return;
}
@@ -473,7 +473,7 @@ function parseTelemetry(dv) {
recalCount = view.getUint16(20, true);
chargeStatus= view.getUint8(22);
if (view.byteLength >= 28) battVoltage = view.getFloat32(24, true);
} catch(e) { log(`parseTelemetry: parse error at offset ${e.message}`,'err'); return; }
} catch(e) { log(`parseTelemetry: parse error at offset - ${e.message}`,'err'); return; }
document.getElementById('telTemp').textContent = temp.toFixed(1)+'°';
document.getElementById('telUptime').textContent = formatUptime(uptime);
@@ -504,7 +504,7 @@ function clearTelemetry() {
document.getElementById(id).textContent='--');
}
// ── Battery & Charge UI ───────────────────────────────────────────────────────
// Battery & Charge UI
function updateBatteryBar(pct, status) {
document.getElementById('battBar').style.display='flex';
document.getElementById('battPct').textContent=pct+'%';
@@ -528,7 +528,7 @@ function updateChargeUI() {
if (currentBattPct!==null) updateBatteryBar(currentBattPct, currentChargeStatus);
}
// ── Advanced toggle ───────────────────────────────────────────────────────
// Advanced toggle
function toggleAdvanced(on) {
advancedMode = on;
localStorage.setItem('advanced', on);
@@ -538,7 +538,7 @@ function toggleAdvanced(on) {
document.getElementById('chargeInfo').style.gridTemplateColumns = on ? '1fr 1fr 1fr 1fr' : '1fr 1fr 1fr';
}
// ── IMU Debug Recorder ────────────────────────────────────────────────────────
// IMU Debug Recorder
let debugModalOpen = false;
let debugRecording = false;
let debugBuffer = [];
@@ -631,7 +631,7 @@ function clearDebugRec() {
document.getElementById('debugRecCount').textContent = '0 samples';
}
// ── Param display ─────────────────────────────────────────────────────────────
// Param display
function updateDisplay(key, val) {
const map = {
sensitivity: ['valSensitivity', v=>parseFloat(v).toFixed(0)],
@@ -644,7 +644,7 @@ function updateDisplay(key, val) {
document.getElementById(id).textContent = fmt(val);
}
// ── Status UI ────────────────────────────────────────────────────────────────
// Status UI
function setStatus(state) {
const pill=document.getElementById('statusPill');
document.getElementById('statusText').textContent={connected:'CONNECTED',connecting:'CONNECTING…',disconnected:'DISCONNECTED'}[state];
@@ -673,7 +673,7 @@ function onDisconnected() {
document.getElementById('badgeCharging').classList.remove('show');
document.getElementById('badgeFull').classList.remove('show');
imuSubscribed = false; vizPaused = true; vizUpdateIndicator(); streamDiagReset();
document.getElementById('orientLabel').textContent = ' not streaming ';
document.getElementById('orientLabel').textContent = '- not streaming -';
document.getElementById('hashMismatchBanner').style.display = 'none';
clearTelemetry();
if (!userDisconnected && document.getElementById('autoReconnect').checked && savedDevice) {
@@ -695,11 +695,11 @@ function onDisconnected() {
}
}
// ── IMU Stream + Visualiser ──────────────────────────────────────────────────
// IMU Stream + Visualiser
// ImuPacket (14 bytes LE):
// int16 gyroX_mDPS [0], int16 gyroZ_mDPS [2]
// int16 accelX_mg [4], int16 accelY_mg [6], int16 accelZ_mg [8]
// int8 moveX [10], int8 moveY [11], uint8 flags [12], uint8 pad [13]
// int16 gyroX_mDPS [0], int16 gyroZ_mDPS [2]
// int16 accelX_mg [4], int16 accelY_mg [6], int16 accelZ_mg [8]
// int8 moveX [10], int8 moveY [11], uint8 flags [12], uint8 pad [13]
const canvas = document.getElementById('vizCanvas');
const ctx = canvas.getContext('2d');
const TRAIL_LEN = 120;
@@ -707,7 +707,7 @@ let cursorX = canvas.width/2, cursorY = canvas.height/2, trail = [];
let vizPaused = true;
let imuSubscribed = false;
// ── Stream diagnostics ────────────────────────────────────────────────────────
// Stream diagnostics
let streamPktCount = 0; // packets received this second
let streamPktTotal = 0; // lifetime packet count
let streamLastPktT = 0; // timestamp of last packet (for gap detection)
@@ -722,7 +722,7 @@ function streamDiagReset() {
function streamDiagPkt() {
const now = Date.now();
// Gap detection warn if >300ms since last packet while streaming
// Gap detection - warn if >300ms since last packet while streaming
if (streamLastPktT) {
const gap = now - streamLastPktT;
if (gap > 300) log(`[STREAM] gap ${gap}ms (pkt #${streamPktTotal})`, 'warn');
@@ -731,10 +731,10 @@ function streamDiagPkt() {
streamPktCount++;
streamPktTotal++;
// Reset freeze watchdog 1.5s without a packet = freeze
// Reset freeze watchdog - 1.5s without a packet = freeze
if (streamFreezeTimer) clearTimeout(streamFreezeTimer);
streamFreezeTimer = setTimeout(() => {
log(`[STREAM] FROZEN no packet for 1.5s (total rx: ${streamPktTotal})`, 'err');
log(`[STREAM] FROZEN - no packet for 1.5s (total rx: ${streamPktTotal})`, 'err');
streamFreezeTimer = null;
}, 1500);
@@ -750,7 +750,7 @@ function streamDiagPkt() {
// Roll compensation is done entirely in firmware (calibrateGyroBias computes
// rollSin/rollCos from boot-pose accel and applies the rotation before moveX/moveY).
// The web visualiser just uses moveX/moveY directly no re-rotation needed here.
// The web visualiser just uses moveX/moveY directly - no re-rotation needed here.
function resetOrient() {} // kept for call-site compatibility
function vizUpdateIndicator() {
@@ -782,7 +782,7 @@ async function vizSetPaused(paused) {
await chars.imuStream.stopNotifications();
imuSubscribed = false;
streamDiagReset();
document.getElementById('orientLabel').textContent = ' not streaming ';
document.getElementById('orientLabel').textContent = '- not streaming -';
} catch(e) { log(`IMU stream stop failed: ${e.message}`,'err'); }
}
vizUpdateIndicator();
@@ -792,7 +792,7 @@ function parseImuStream(dv) {
let view;
try {
view = dv instanceof DataView ? new DataView(dv.buffer, dv.byteOffset, dv.byteLength) : new DataView(dv);
} catch(e) { log(`parseImuStream: DataView wrap failed ${e.message}`,'err'); return; }
} catch(e) { log(`parseImuStream: DataView wrap failed - ${e.message}`,'err'); return; }
if (view.byteLength < 14) {
const bytes = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
@@ -811,7 +811,7 @@ function parseImuStream(dv) {
moveX = view.getInt8(10);
moveY = view.getInt8(11);
flags = view.getUint8(12);
} catch(e) { log(`parseImuStream: parse error ${e.message}`,'err'); return; }
} catch(e) { log(`parseImuStream: parse error - ${e.message}`,'err'); return; }
// Feed debug recorder (even when viz is paused)
feedDebugRow(gyroX, gyroZ, accelX, accelY, accelZ, moveX, moveY, flags);
@@ -826,7 +826,7 @@ function parseImuStream(dv) {
updateAxisBar('gz', -gyroX, 30000);
if (!idle) {
// moveX/moveY are already roll-corrected by firmware use them directly
// moveX/moveY are already roll-corrected by firmware - use them directly
cursorX = Math.max(4, Math.min(canvas.width - 4, cursorX + moveX * 1.5));
cursorY = Math.max(4, Math.min(canvas.height - 4, cursorY + moveY * 1.5));
}
@@ -894,7 +894,7 @@ function drawInitState() {
ctx.fillStyle=cssVar('--canvas-idle-text');ctx.font='10px Share Tech Mono,monospace';
ctx.textAlign='center';ctx.fillText('connect to activate stream',W/2,H/2+4);ctx.textAlign='left';
}
// ── 3D Orientation Viewer ─────────────────────────────────────────────────────
// 3D Orientation Viewer
// Device box: L=115mm (X), W=36mm (Y), H=20mm (Z)
// Complementary filter mirrors firmware: α=0.96, dt from packet rate (~50ms)
const ORIENT_ALPHA = 0.96;
@@ -932,7 +932,7 @@ function initOrientViewer() {
orientEdges = new THREE.LineSegments(new THREE.EdgesGeometry(geo), edgeMat);
orientMesh.add(orientEdges);
// "Front" face marker small arrow along +X (length axis)
// "Front" face marker - small arrow along +X (length axis)
const arrowGeo = new THREE.ConeGeometry(0.02, 0.07, 6);
arrowGeo.rotateZ(-Math.PI / 2);
arrowGeo.translate(DEVICE_L / 2 + 0.04, 0, 0);
@@ -982,7 +982,7 @@ function orientFeedIMU(ax, ay, az, gyX_mDPS, gyZ_mDPS) {
qAccel.copy(orientQ);
}
// Gyro integration firmware sends gyroX (pitch) and gyroZ (yaw), mDPS
// Gyro integration - firmware sends gyroX (pitch) and gyroZ (yaw), mDPS
// Map to Three.js axes: gyroZ→world Y, gyroX→world X
const gyRad = gyX_mDPS * (Math.PI / 180) / 1000;
const gzRad = gyZ_mDPS * (Math.PI / 180) / 1000;
@@ -1001,7 +1001,7 @@ function orientFeedIMU(ax, ay, az, gyX_mDPS, gyZ_mDPS) {
orientRenderer.render(orientScene, orientCamera);
}
// ── Theme ─────────────────────────────────────────────────────────────────────
// Theme
const THEMES = ['auto','dark','light'];
const THEME_LABELS = {auto:'AUTO',dark:'DARK',light:'LIGHT'};
let themeIdx = 0;