Remove unnecessary comments, clean up code
This commit is contained in:
+60
-60
@@ -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;
|
||||
|
||||
+8
-8
@@ -39,7 +39,7 @@
|
||||
|
||||
<main id="mainContent">
|
||||
|
||||
<!-- ── col-left: cursor motion ──────────────────────────────── -->
|
||||
<!-- col-left: cursor motion -->
|
||||
<div class="col-left">
|
||||
|
||||
<div class="section-label">Motion Parameters</div>
|
||||
@@ -51,7 +51,7 @@
|
||||
<div class="param-value" id="valSensitivity">600</div>
|
||||
</div>
|
||||
<div class="param">
|
||||
<div><div class="param-label">Dead Zone</div><div class="param-desc">Noise floor (rad/s) — raise to reduce drift</div></div>
|
||||
<div><div class="param-label">Dead Zone</div><div class="param-desc">Noise floor (rad/s) - raise to reduce drift</div></div>
|
||||
<input type="range" id="slDeadZone" min="0.005" max="0.2" step="0.005" value="0.06"
|
||||
oninput="updateDisplay('deadZone',this.value)" onchange="writeConfigBlob()">
|
||||
<div class="param-value" id="valDeadZone">0.060</div>
|
||||
@@ -134,7 +134,7 @@
|
||||
|
||||
</div><!-- /col-left -->
|
||||
|
||||
<!-- ── col-mid: button configuration ───────────────────────── -->
|
||||
<!-- col-mid: button configuration -->
|
||||
<div class="col-mid">
|
||||
|
||||
<div class="section-label">Tap Configuration</div>
|
||||
@@ -146,7 +146,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="param">
|
||||
<div><div class="param-label">Tap Freeze Sensitivity</div><div class="param-desc">Jerk² threshold — lower = more aggressive cursor freeze during taps</div></div>
|
||||
<div><div class="param-label">Tap Freeze Sensitivity</div><div class="param-desc">Jerk² threshold - lower = more aggressive cursor freeze during taps</div></div>
|
||||
<input type="range" id="slJerkThreshold" min="500" max="10000" step="100" value="2000"
|
||||
oninput="updateDisplay('jerkThreshold',this.value)" onchange="writeConfigBlob()">
|
||||
<div class="param-value" id="valJerkThreshold">2000</div>
|
||||
@@ -183,7 +183,7 @@
|
||||
|
||||
<div class="section-label">Physical Buttons</div>
|
||||
<div class="card">
|
||||
<!-- ── XIAO nRF52840 Sense pin diagram ─────────────────── -->
|
||||
<!-- XIAO nRF52840 Sense pin diagram -->
|
||||
<div class="xiao-wrap">
|
||||
<svg id="xiaoSvg" viewBox="0 0 200 278" xmlns="http://www.w3.org/2000/svg" style="display:block;width:100%;max-width:200px">
|
||||
<!-- USB-C connector -->
|
||||
@@ -301,7 +301,7 @@
|
||||
<div class="cmd-grid">
|
||||
<button class="cmd-btn calibrate" id="btnCal" onclick="sendCalibrate()" disabled>
|
||||
<span class="cmd-icon">⊕</span><span>Calibrate Gyro</span>
|
||||
<span class="cmd-desc">Hold device still — recalculates bias + records cal temperature.</span>
|
||||
<span class="cmd-desc">Hold device still - recalculates bias + records cal temperature.</span>
|
||||
</button>
|
||||
<button class="cmd-btn reset" id="btnReset" onclick="confirmReset()" disabled>
|
||||
<span class="cmd-icon">⚠</span><span>Factory Reset</span>
|
||||
@@ -314,7 +314,7 @@
|
||||
|
||||
</div><!-- /col-mid -->
|
||||
|
||||
<!-- ── col-right: live monitoring ───────────────────────────── -->
|
||||
<!-- col-right: live monitoring -->
|
||||
<div class="col-right">
|
||||
|
||||
<div class="section-label">Live Cursor Visualiser</div>
|
||||
@@ -350,7 +350,7 @@
|
||||
<div class="section-label">Device Orientation</div>
|
||||
<div class="card orient-card">
|
||||
<canvas id="orientCanvas"></canvas>
|
||||
<div style="font-size:9px;color:var(--label);text-align:center;margin-top:6px" id="orientLabel">— not streaming —</div>
|
||||
<div style="font-size:9px;color:var(--label);text-align:center;margin-top:6px" id="orientLabel">- not streaming -</div>
|
||||
</div>
|
||||
|
||||
<div class="section-label">Live Telemetry</div>
|
||||
|
||||
+5
-5
@@ -34,7 +34,7 @@
|
||||
--tap-right: rgba(255,61,113,0.35);
|
||||
}
|
||||
|
||||
/* ── Light theme (explicit) ──────────────────────────────────────────────── */
|
||||
/* Light theme (explicit) */
|
||||
:root.theme-light {
|
||||
--bg: #f0f2f5;
|
||||
--panel: #ffffff;
|
||||
@@ -66,7 +66,7 @@
|
||||
}
|
||||
|
||||
|
||||
/* ── Auto light (OS hint; explicit class overrides) ──────────────────────── */
|
||||
/* Auto light (OS hint; explicit class overrides) */
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root:not(.theme-dark) {
|
||||
--bg: #f0f2f5;
|
||||
@@ -153,7 +153,7 @@
|
||||
.col-mid { display:grid; gap:12px; }
|
||||
.col-right { display:grid; gap:12px; position:sticky; top:80px; }
|
||||
|
||||
/* ── XIAO pin diagram ─────────────────────────────────────── */
|
||||
/* XIAO pin diagram */
|
||||
.xiao-wrap { display:flex; flex-direction:column; align-items:center; padding:8px 0 14px; }
|
||||
.pin-legend { display:flex; gap:20px; justify-content:center; font-family:var(--mono); font-size:9px; margin-top:10px; letter-spacing:0.08em; }
|
||||
.pleg.left { color:var(--ok); }
|
||||
@@ -161,7 +161,7 @@
|
||||
.pleg.mid { color:var(--accent); }
|
||||
.xiao-divider { border:none; border-top:1px solid var(--border); margin:0 -20px 12px; }
|
||||
|
||||
/* ── Responsive ───────────────────────────────────────────── */
|
||||
/* Responsive */
|
||||
@media (max-width:1100px) {
|
||||
main { grid-template-columns:1fr 380px; grid-template-rows:auto auto; }
|
||||
.col-left { grid-column:1; grid-row:1; }
|
||||
@@ -288,7 +288,7 @@
|
||||
.btn-confirm { border-color:var(--accent2); color:var(--accent2); }
|
||||
.btn-confirm:hover { background:var(--accent2); color:var(--bg); }
|
||||
|
||||
/* ── Debug modal ────────────────────────────────────────────────────────── */
|
||||
/* Debug modal */
|
||||
.debug-modal { max-width:720px; padding:20px; border-color:var(--accent); }
|
||||
.debug-modal h3 { color:var(--accent); margin-bottom:0; }
|
||||
.debug-header { display:flex; justify-content:space-between; align-items:center; margin-bottom:14px; }
|
||||
|
||||
Reference in New Issue
Block a user