Attempt to add jerk correction

This commit is contained in:
2026-03-02 23:53:10 +01:00
parent 4768754bef
commit a666304013
6 changed files with 215 additions and 14 deletions
+109 -3
View File
@@ -11,7 +11,7 @@ const CHR = {
// Local shadow of the current config (kept in sync with device)
const config = { sensitivity:600, deadZone:0.06, accelStrength:0.08, curve:0, axisFlip:0, chargeMode:1,
tapThreshold:12, tapAction:0, tapKey:0, tapMod:0 };
tapThreshold:12, tapAction:0, tapKey:0, tapMod:0, jerkThreshold:2000 };
let device=null, server=null, chars={}, userDisconnected=false;
let currentChargeStatus=0, currentBattPct=null, currentBattVoltage=null;
@@ -222,6 +222,9 @@ async function readConfigBlob() {
config.tapKey = view.getUint8(17);
config.tapMod = view.getUint8(18);
}
if (view.byteLength >= 24) {
config.jerkThreshold = view.getFloat32(20, true);
}
applyConfigToUI();
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'); }
@@ -238,6 +241,8 @@ function applyConfigToUI() {
document.getElementById('flipX').checked = !!(config.axisFlip & 1);
document.getElementById('flipY').checked = !!(config.axisFlip & 2);
setChargeModeUI(config.chargeMode);
document.getElementById('slJerkThreshold').value = config.jerkThreshold;
updateDisplay('jerkThreshold', config.jerkThreshold);
document.getElementById('slTapThreshold').value = config.tapThreshold;
updateDisplay('tapThreshold', config.tapThreshold);
setTapActionUI(config.tapAction);
@@ -267,9 +272,10 @@ async function _doWriteConfigBlob() {
| (document.getElementById('tapModShift').checked ? 0x02 : 0)
| (document.getElementById('tapModAlt').checked ? 0x04 : 0)
| (document.getElementById('tapModGui').checked ? 0x08 : 0);
config.jerkThreshold = +document.getElementById('slJerkThreshold').value;
// config.curve, config.chargeMode, config.tapAction, config.tapKey updated directly
const buf = new ArrayBuffer(20);
const buf = new ArrayBuffer(24);
const view = new DataView(buf);
view.setFloat32(0, config.sensitivity, true);
view.setFloat32(4, config.deadZone, true);
@@ -282,6 +288,7 @@ async function _doWriteConfigBlob() {
view.setUint8(17, config.tapKey);
view.setUint8(18, config.tapMod);
view.setUint8(19, 0);
view.setFloat32(20, config.jerkThreshold, true);
try {
await gattWrite(chars.configBlob, buf);
@@ -440,16 +447,111 @@ function toggleAdvanced(on) {
advancedMode = on;
localStorage.setItem('advanced', on);
document.getElementById('ciVoltItem').style.display = on ? '' : 'none';
document.getElementById('debugBtn').style.display = on ? '' : 'none';
// Switch charge-info grid between 3 and 4 columns
document.getElementById('chargeInfo').style.gridTemplateColumns = on ? '1fr 1fr 1fr 1fr' : '1fr 1fr 1fr';
}
// ── IMU Debug Recorder ────────────────────────────────────────────────────────
let debugModalOpen = false;
let debugRecording = false;
let debugBuffer = [];
const DEBUG_LIVE_ROWS = 40;
let debugLiveRing = [];
let debugT0 = 0;
function openDebugModal() {
debugModalOpen = true;
debugT0 = Date.now();
debugLiveRing = [];
document.getElementById('debugOverlay').classList.add('show');
// Auto-start IMU stream if not already running
if (!imuSubscribed && chars.imuStream) vizSetPaused(false);
}
function closeDebugModal() {
debugModalOpen = false;
document.getElementById('debugOverlay').classList.remove('show');
}
function feedDebugRow(gyroX, gyroZ, accelX, accelY, accelZ, moveX, moveY, flags) {
if (!debugModalOpen) return;
const ms = Date.now() - debugT0;
const row = { ms, gyroX, gyroZ, accelX, accelY, accelZ, moveX, moveY, flags };
// Live ring buffer
debugLiveRing.push(row);
if (debugLiveRing.length > DEBUG_LIVE_ROWS) debugLiveRing.shift();
// Recording buffer
if (debugRecording) {
debugBuffer.push(row);
document.getElementById('debugRecCount').textContent = debugBuffer.length + ' samples';
}
// Shock indicator
const shocked = !!(flags & 0x08);
const badge = document.getElementById('debugShockBadge');
badge.classList.toggle('active', shocked);
// Render live table
const tbody = document.getElementById('debugRows');
tbody.innerHTML = '';
for (const r of debugLiveRing) {
const f = [];
if (r.flags & 0x01) f.push('idle');
if (r.flags & 0x02) f.push('tap1');
if (r.flags & 0x04) f.push('tap2');
if (r.flags & 0x08) f.push('shock');
const tr = document.createElement('tr');
if (r.flags & 0x08) tr.className = 'shock-row';
tr.innerHTML =
`<td>${r.ms}</td><td>${r.gyroX}</td><td>${r.gyroZ}</td>` +
`<td>${r.accelX}</td><td>${r.accelY}</td><td>${r.accelZ}</td>` +
`<td>${r.moveX}</td><td>${r.moveY}</td><td>${f.join(' ')}</td>`;
tbody.appendChild(tr);
}
tbody.parentElement.parentElement.scrollTop = tbody.parentElement.parentElement.scrollHeight;
}
function toggleDebugRec() {
debugRecording = !debugRecording;
const btn = document.getElementById('debugRecBtn');
btn.classList.toggle('recording', debugRecording);
btn.textContent = debugRecording ? '■ STOP' : '● REC';
if (debugRecording) { debugBuffer = []; debugT0 = Date.now(); }
document.getElementById('debugRecCount').textContent = debugBuffer.length + ' samples';
}
function saveDebugCSV() {
if (!debugBuffer.length) { log('No recorded data to save','warn'); return; }
const header = 'ms,gyroX_mDPS,gyroZ_mDPS,accelX_mg,accelY_mg,accelZ_mg,moveX,moveY,flags\n';
const rows = debugBuffer.map(r =>
`${r.ms},${r.gyroX},${r.gyroZ},${r.accelX},${r.accelY},${r.accelZ},${r.moveX},${r.moveY},${r.flags}`
).join('\n');
const blob = new Blob([header + rows], { type: 'text/csv' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url; a.download = `imu_debug_${new Date().toISOString().slice(0,19).replace(/:/g,'-')}.csv`;
a.click(); URL.revokeObjectURL(url);
log(`Saved ${debugBuffer.length} samples as CSV`,'ok');
}
function clearDebugRec() {
debugBuffer = [];
debugRecording = false;
const btn = document.getElementById('debugRecBtn');
btn.classList.remove('recording');
btn.textContent = '● REC';
document.getElementById('debugRecCount').textContent = '0 samples';
}
// ── Param display ─────────────────────────────────────────────────────────────
function updateDisplay(key, val) {
const map = {
sensitivity: ['valSensitivity', v=>parseFloat(v).toFixed(0)],
deadZone: ['valDeadZone', v=>parseFloat(v).toFixed(3)],
accel: ['valAccel', v=>parseFloat(v).toFixed(2)],
jerkThreshold:['valJerkThreshold',v=>parseFloat(v).toFixed(0)],
tapThreshold: ['valTapThreshold', v=>(parseFloat(v)*62.5).toFixed(0)+' mg'],
};
const [id,fmt] = map[key];
@@ -601,7 +703,6 @@ async function vizSetPaused(paused) {
}
function parseImuStream(dv) {
if (vizPaused) return;
let view;
try {
view = dv instanceof DataView ? new DataView(dv.buffer, dv.byteOffset, dv.byteLength) : new DataView(dv);
@@ -625,6 +726,11 @@ function parseImuStream(dv) {
moveY = view.getInt8(11);
flags = view.getUint8(12);
} 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);
if (vizPaused) return;
const idle = !!(flags & 0x01);
const single = !!(flags & 0x02);
const dbl = !!(flags & 0x04);