A Sensor A

Device—
Browser device ID—
Either sensor type fits this slot — NDSA advertises as IMU_XX (0xFEE7), MetaMotion as MetaWear. The protocol is auto-detected on connect. Wake a quiet sensor (NDSA Wakeup button / MetaMotion push button) if it doesn’t appear.

B Sensor B

Device—
Browser device ID—
Connect any combination — NDSA + MetaMotion, two NDSAs, or two MetaMotions. Use Identify to flash a sensor’s LED and tell twins apart.

Test Control

Anot connected
Bnot connected
For the report, do two short runs with the sensors strapped rigidly together: a Raw IMU run (accel/gyro/mag accuracy) and a Fusion run (orientation). Each: 10 s still → 20 s movement → 10 s still → Stop → Generate report.

Test Settings (applied to both)

Accel/gyro changes are written to the sensors when you click Apply to sensors (then read back automatically), and again at every Start test, so a test always runs with what is selected here.
Results appear in the Sensor Settings table below.
Only values supported by both sensor types are listed; on Start the identical configuration is written to every connected sensor (the log shows the exact bytes). Rates: MetaWear streams run at 100 Hz (mag 20/25 Hz — BMM150 limit); the NDSA is fixed at 104 Hz by firmware — v251000 has no rate command, so it cannot be set to 100 Hz (the report resamples to a common grid). Raw mode uses MetaWear packed streaming (3 samples/notification) so full-rate accel+gyro+mag fits BLE; fusion mode streams the quaternion only from MetaWear to stay within bandwidth. ±125 dps is raw-mode only (MetaWear fusion limit). NDSA always runs production cmd 0x4; its mag range is fixed (no command in v251000).

Sensor Settings — read from hardware

Click “Read sensor settings (both)” to read each sensor’s live configuration.

Calibration

NDSA: runs the manufacturer’s calibration commands (0x0 collect, 0x3 write, 0x10 read, 0x1 verify). The spec does not say how raw samples become calibration values, so the values are computed here with standard methods (gyro mean, accelerometer sphere fit, magnetometer ellipsoid fit) — console method, pending Shanghai Zhenneng confirmation. The current calibration is read and kept before any write, so it can always be reverted; download a backup first for safety. Always finish with Verify.
Connect a sensor to calibrate it.

Effective Sampling Rates

Nominal = rate the sensor is configured for · Live = measured over the last 2 s · Run avg = measured over the whole capture · Delivered = run avg ÷ nominal. NDSA rates use its on-board millisecond timestamps; MetaWear rates use arrival time (it sends no timestamps). On-board streams are sampled inside the sensor to feed the fusion algorithm but are not transmitted during a Fusion test.
Live signals — side by side

A Accelerometer (g)

XYZ

B Accelerometer (g)

XYZ

A Gyroscope (dps)

XYZ

B Gyroscope (dps)

XYZ

A Magnetometer

XYZ

B Magnetometer

XYZ

A Quaternion

Q0/WQ1/XQ2/YQ3/Z

B Quaternion

Q0/WQ1/XQ2/YQ3/Z
Direct comparison — magnitudes on one timeline (frame-independent)

|Acceleration| (g)

Sensor ASensor B

|Angular rate| (dps)

Sensor ASensor B

Comparison Report

`; $('btnReportDl').disabled=false; log('Comparison report generated.','ok'); } function downloadReport(){ if(lastReportDoc) saveBlob(lastReportDoc, `sensor_comparison_${stamp()}.html`, 'text/html'); } /* ════════════════════════════ WIRE UP ════════════════════════════ */ slots.forEach(s=>{ $('btnConn'+s.key).onclick=()=>connectSlot(s); $('btnDisc'+s.key).onclick=()=>disconnectSlot(s); $('btnRen'+s.key).onclick=()=>s.drv&&s.drv.rename(); $('btnBatt'+s.key).onclick=()=>s.drv&&s.drv.battery(); $('btnInfo'+s.key).onclick=()=>s.drv&&s.drv.info(); $('btnLed'+s.key).onclick=()=>s.drv&&s.drv.identify(); $('btnSleep'+s.key).onclick=()=>s.drv&&s.drv.sleep&&s.drv.sleep(); $('btnCsv'+s.key).onclick=()=>s.drv&&s.drv.csv(); }); $('btnStart').onclick=startTest; $('btnStop').onclick=stopTest; $('btnTare').onclick=tareBoth; $('btnClear').onclick=clearAll; $('btnShowCfg').onclick=showSettings; $('btnApply').onclick=applyToSensors; $('calOut').addEventListener('click', e=>{ const b=e.target.closest('button[data-act]'); if(b&&!b.disabled) calAction(b.dataset.act, b.dataset.slot, b.dataset.kind); }); $('calFile').addEventListener('change', e=>calRestoreFile(e.target.files[0])); $('btnReport').onclick=generateReport; $('btnReportDl').onclick=downloadReport; if(!navigator.bluetooth){ $('noBle').style.display='block'; $('btnConnA').disabled=true; $('btnConnB').disabled=true; }else{ log('Ready. Each slot accepts either sensor type (auto-detected). Connect up to two sensors, then "Start test" streams them together with identical settings — all MetaWear streams at 100 Hz, NDSA at its firmware-fixed 104 Hz.'); } updateGyroOptions(); updateUI();