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;
|
||||
|
||||
Reference in New Issue
Block a user