Skip to content

Commit 6614d9d

Browse files
committed
adds docs, improves ux
1 parent b076bcc commit 6614d9d

File tree

4 files changed

+216
-87
lines changed

4 files changed

+216
-87
lines changed

CLAUDE.md

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,121 @@ Dashboard validates threshold ranges before sending to ESP32:
514514
**Changed from WebSocket (ap_enabled branch):**
515515
Originally used WebSocket push at 30Hz but this caused thread starvation. Refactored to HTTP polling for reliability.
516516

517+
### Autotune System
518+
519+
**Overview:**
520+
Autotune learns vehicle-specific mode detection thresholds from a calibration drive. Instead of generic presets, thresholds are derived from actual sensor data captured during normal driving.
521+
522+
**Why Autotune?**
523+
- Every vehicle has different suspension, tire grip, weight distribution
524+
- Generic thresholds may trigger too early (sensitive car) or too late (stiff car)
525+
- Calibration captures your specific vehicle's g-force characteristics
526+
527+
**Data Flow:**
528+
```
529+
Telemetry → Event Detection → Categorization → Median Calculation → Scaling → All Profiles
530+
│ │ │ │ │ │
531+
└─►ax,ay,wz └─►EMA filter └─►ACCEL/BRAKE/ └─►P50 values └─►×scale └─►localStorage
532+
α=0.35 CORNER bins (robust) factors bb_profiles
533+
```
534+
535+
**Event Detection Algorithm:**
536+
```javascript
537+
// Detect events from telemetry stream
538+
const EMA_ALPHA = 0.35; // Smoothing factor
539+
const EVENT_MIN_DURATION = 200; // ms - reject transients
540+
const EVENT_TIMEOUT = 300; // ms - gap to end event
541+
542+
// Smooth raw sensor data
543+
ax_ema = ax_ema * (1 - EMA_ALPHA) + raw_ax * EMA_ALPHA;
544+
545+
// Detect event start/end based on magnitude threshold
546+
// Record peak values during event
547+
// Categorize: positive ax → ACCEL, negative ax → BRAKE, high ay+wz → CORNER
548+
```
549+
550+
**Threshold Calculation:**
551+
```javascript
552+
// For each event category (ACCEL, BRAKE, CORNER):
553+
const sortedPeaks = events.map(e => e.peak).sort((a,b) => a - b);
554+
const P50 = sortedPeaks[Math.floor(sortedPeaks.length * 0.5)]; // Median
555+
556+
// Entry threshold: 70% of median (triggers before typical peak)
557+
const entry = P50 * 0.7;
558+
559+
// Exit threshold: 50% of entry (hysteresis prevents oscillation)
560+
const exit = entry * 0.5;
561+
```
562+
563+
**Profile Scaling System:**
564+
Single city calibration generates all 4 profiles using physics-based multipliers:
565+
566+
```javascript
567+
const PROFILE_SCALES = {
568+
track: { acc: 2.5, brake: 2.0, lat: 3.0, yaw: 2.5, min_speed: 5.0 },
569+
canyon: { acc: 1.5, brake: 1.5, lat: 1.8, yaw: 1.6, min_speed: 3.0 },
570+
city: { acc: 1.0, brake: 1.0, lat: 1.0, yaw: 1.0, min_speed: 2.0 },
571+
highway: { acc: 0.8, brake: 0.7, lat: 0.6, yaw: 0.6, min_speed: 12.0 }
572+
};
573+
574+
// Scale from city baseline
575+
track.acc_entry = city.acc_entry * 2.5; // Track expects 2.5× higher g-forces
576+
highway.lat_entry = city.lat_entry * 0.6; // Highway lane changes are gentler
577+
```
578+
579+
**localStorage Data Structure:**
580+
```javascript
581+
// Stored as 'bb_profiles' in localStorage
582+
{
583+
"track": {
584+
"acc": 0.25, // Entry threshold (g, converted to m/s²)
585+
"acc_exit": 0.125,
586+
"brake": 0.36,
587+
"brake_exit": 0.18,
588+
"lat": 0.36,
589+
"lat_exit": 0.18,
590+
"yaw": 0.125,
591+
"min_speed": 5.0,
592+
"desc": "Racing/track days"
593+
},
594+
"canyon": { ... },
595+
"city": { ... },
596+
"highway": { ... },
597+
"calibrated_at": "2024-01-15T10:30:00.000Z",
598+
"vehicle_id": "optional-user-label"
599+
}
600+
```
601+
602+
**Physics Validation Metrics:**
603+
Autotune validates calibration quality using physics cross-checks:
604+
- GPS↔Sensor speed correlation (should be >0.8)
605+
- Accel events should show speed increasing
606+
- Brake events should show speed decreasing
607+
- Lateral events should correlate with heading change
608+
- Centripetal validation: ay ≈ v²/r (from wz)
609+
610+
**Confidence Levels:**
611+
Based on sample count per event category:
612+
- HIGH: n ≥ 15 events (median very stable)
613+
- MEDIUM: n = 8-14 events (usable, some variance)
614+
- LOW: n = 3-7 events (may need more driving)
615+
- INSUFFICIENT: n < 3 (cannot compute reliable threshold)
616+
617+
**Export Format:**
618+
JSON export includes:
619+
- All 4 scaled profiles with thresholds
620+
- Raw event data (peaks, durations, timestamps)
621+
- Scaling factors used
622+
- Validation metrics
623+
- Confidence per category
624+
- Timestamp and optional vehicle ID
625+
626+
**Integration with Dashboard:**
627+
- Preset buttons show green calibration dot when `bb_profiles` exists
628+
- Clicking preset loads calibrated values (or defaults if uncalibrated)
629+
- Export button downloads comprehensive JSON for analysis
630+
- Progress bar shows real-time event capture during calibration
631+
517632
### udp_stream.rs - UDP Client (Station Mode)
518633

519634
**Features:**

README.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,69 @@ You can trigger recalibration from the dashboard:
643643

644644
---
645645

646+
## Autotune (Threshold Calibration)
647+
648+
The **Autotune** feature learns your vehicle's characteristics from a single city drive and automatically generates optimized thresholds for all 4 driving profiles.
649+
650+
### Why Autotune?
651+
652+
Default presets use generic thresholds, but every vehicle is different:
653+
- A sports car can pull 1.0g lateral; a minivan might max at 0.5g
654+
- Your driving style affects typical G-forces
655+
- One calibration drive personalizes ALL presets to YOUR vehicle
656+
657+
### How to Use Autotune
658+
659+
1. **Open the dashboard** at `http://192.168.71.1`
660+
2. **Tap "Autotune"** in the menu
661+
3. **Wait for GPS lock** (required before starting)
662+
4. **Select a scenario** (Guided, Parking Lot, or Free Drive)
663+
5. **Tap "Start Calibration"** and drive normally for 10-20 minutes
664+
6. **Collect events**: accelerations, brakes, and turns
665+
7. **Review results** - the system generates all 4 profiles
666+
8. **Tap "Apply"** to save and activate
667+
668+
### What Gets Generated
669+
670+
From your city driving baseline, Autotune generates 4 vehicle-specific profiles:
671+
672+
| Profile | Scaling | Use Case |
673+
|---------|---------|----------|
674+
| **Track** | 2.0-3.0× baseline | Racing, track days |
675+
| **Canyon** | 1.5-1.8× baseline | Spirited mountain roads |
676+
| **City** | 1.0× (your baseline) | Daily driving |
677+
| **Highway** | 0.6-0.8× baseline | Highway cruising |
678+
679+
### Confidence Levels
680+
681+
The more events you collect, the more accurate the thresholds:
682+
683+
| Events | Confidence | Recommendation |
684+
|--------|------------|----------------|
685+
| 3-7 each | Low | Keep driving |
686+
| 8-14 each | Medium | Good for most uses |
687+
| 15+ each | High | Excellent accuracy |
688+
689+
### Export Data
690+
691+
Autotune exports comprehensive JSON for analysis:
692+
- All 4 generated profiles
693+
- Raw event data (peaks, durations, speed changes)
694+
- Physics validation metrics
695+
- Scaling factors used
696+
697+
This data helps evaluate calibration quality and can be used for post-analysis.
698+
699+
### Tips for Best Results
700+
701+
- **Drive normally** - don't exaggerate maneuvers
702+
- **Include variety** - different turn directions, brake intensities
703+
- **Flat ground preferred** - inclines can affect readings
704+
- **More driving = better** - the progress bar is just the minimum
705+
- **GPS lock required** - ensures accurate speed validation
706+
707+
---
708+
646709
## Mobile Dashboard
647710

648711
The firmware includes a built-in web dashboard that runs directly on the ESP32. No external server needed - just connect your phone and view live telemetry.

docs/index.html

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,6 +1063,21 @@ <h3>ZUPT</h3>
10631063
<p>Zero-velocity updates prevent drift. Bias continuously estimated when stationary.</p>
10641064
</div>
10651065

1066+
<div class="feature-card">
1067+
<div class="feature-icon">
1068+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1069+
<path d="M12 20V10"/>
1070+
<path d="M18 20V4"/>
1071+
<path d="M6 20v-4"/>
1072+
<circle cx="12" cy="7" r="2"/>
1073+
<circle cx="6" cy="13" r="2"/>
1074+
<circle cx="18" cy="7" r="2"/>
1075+
</svg>
1076+
</div>
1077+
<h3>Autotune</h3>
1078+
<p>Learn vehicle-specific thresholds from a calibration drive. One city drive generates all 4 profiles.</p>
1079+
</div>
1080+
10661081
<div class="feature-card">
10671082
<div class="feature-icon">
10681083
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
@@ -1127,6 +1142,10 @@ <h3>Dashboard Features</h3>
11271142
<svg class="icon-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 6L9 17l-5-5"/></svg>
11281143
Driving presets: Track, Canyon, City, Highway, Custom
11291144
</li>
1145+
<li>
1146+
<svg class="icon-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 6L9 17l-5-5"/></svg>
1147+
Autotune: calibrate thresholds to your vehicle
1148+
</li>
11301149
<li>
11311150
<svg class="icon-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 6L9 17l-5-5"/></svg>
11321151
Record & export to CSV

sensors/blackbox/src/websocket_server.rs

Lines changed: 19 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -663,14 +663,6 @@ body{font-family:-apple-system,system-ui,sans-serif;background:#0a0a0f;color:#f0
663663
.main{flex:1;padding:12px;display:flex;flex-direction:column;gap:10px;overflow-y:auto}
664664
.card{background:#111;border-radius:10px;padding:14px;border:1px solid #1a1a24}
665665
.card-title{font-size:11px;color:#555;text-transform:uppercase;letter-spacing:1px;margin-bottom:10px;display:flex;justify-content:space-between;align-items:center}
666-
.scenario-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}
667-
.scenario{background:#0a0a0f;border:2px solid #1a1a24;border-radius:10px;padding:12px;cursor:pointer;transition:all .2s}
668-
.scenario.active{border-color:#3b82f6;background:linear-gradient(135deg,#0f1a2e,#0a0a0f)}
669-
.scenario:active{transform:scale(0.98)}
670-
.scenario-icon{font-size:24px;margin-bottom:6px}
671-
.scenario-name{font-size:12px;font-weight:600;color:#f0f0f0}
672-
.scenario-desc{font-size:9px;color:#555;margin-top:4px;line-height:1.3}
673-
.scenario-time{font-size:8px;color:#3b82f6;margin-top:6px}
674666
.instructions{background:#0f1a2e;border:1px solid #1e3a5f;border-radius:8px;padding:12px;margin-top:10px}
675667
.instructions-title{font-size:10px;color:#60a5fa;font-weight:600;margin-bottom:8px}
676668
.instructions-text{font-size:11px;color:#888;line-height:1.5}
@@ -742,45 +734,27 @@ body{font-family:-apple-system,system-ui,sans-serif;background:#0a0a0f;color:#f0
742734
<div class="hdr"><a href="/" class="back">← Dashboard</a><div class="logo">AUTOTUNE</div></div>
743735
<div class="main">
744736
745-
<div class="card" id="scenario-card">
746-
<div class="card-title">Select Calibration Scenario</div>
747-
<div class="scenario-grid">
748-
<div class="scenario active" data-scenario="guided">
749-
<div class="scenario-icon">🎯</div>
750-
<div class="scenario-name">Guided</div>
751-
<div class="scenario-desc">Best accuracy. Perform specific maneuvers when prompted.</div>
752-
<div class="scenario-time">~3 min</div>
753-
</div>
754-
<div class="scenario" data-scenario="city">
755-
<div class="scenario-icon">🏙️</div>
756-
<div class="scenario-name">City Loop</div>
757-
<div class="scenario-desc">Drive around the block 5-10 times. Stop signs, turns, traffic.</div>
758-
<div class="scenario-time">~5 min</div>
759-
</div>
760-
<div class="scenario" data-scenario="highway">
761-
<div class="scenario-icon">🛣️</div>
762-
<div class="scenario-name">Highway</div>
763-
<div class="scenario-desc">Highway driving with lane changes and on/off ramps.</div>
764-
<div class="scenario-time">~5 min</div>
765-
</div>
766-
<div class="scenario" data-scenario="parking">
767-
<div class="scenario-icon">🅿️</div>
768-
<div class="scenario-name">Parking Lot</div>
769-
<div class="scenario-desc">Empty lot. Hard stops, tight turns, figure-8s for max G.</div>
770-
<div class="scenario-time">~2 min</div>
771-
</div>
737+
<div class="card">
738+
<div class="card-title">Calibration Drive</div>
739+
<div style="text-align:center;padding:8px 0">
740+
<div style="font-size:28px;margin-bottom:4px">🚗</div>
741+
<div style="font-size:14px;font-weight:600;color:#60a5fa">Drive normally for 10-20 minutes</div>
742+
<div style="font-size:11px;color:#666;margin-top:4px">One drive generates all 4 profiles (Track, Canyon, City, Highway)</div>
772743
</div>
773-
<div class="instructions" id="instructions">
774-
<div class="instructions-title">📋 Guided Calibration Instructions</div>
775-
<div class="instructions-text" id="instructions-text">
744+
<div class="instructions">
745+
<div class="instructions-title">📋 How It Works</div>
746+
<div class="instructions-text">
776747
<ol>
777-
<li>Find a safe, quiet road or empty parking lot</li>
778-
<li>Press Start - events are auto-detected as you drive</li>
779-
<li><b>Accelerate</b> firmly 5+ times (from stops or slow speed)</li>
780-
<li><b>Brake</b> normally 5+ times (to stops or slow down)</li>
781-
<li><b>Turn</b> at intersections or make 10+ turns in a lot</li>
782-
<li>Drive as you <i>normally</i> would - don't overdo it!</li>
748+
<li>Press <b>Start Calibration</b> below</li>
749+
<li>Drive around your neighborhood normally</li>
750+
<li>Include stops, turns, and normal acceleration</li>
751+
<li>The system auto-detects driving events</li>
752+
<li>When progress bar fills, you have enough data</li>
753+
<li>Press <b>Apply</b> to save all 4 profiles</li>
783754
</ol>
755+
<div style="margin-top:8px;padding:8px;background:#0a1a0a;border-radius:6px;font-size:10px;color:#22c55e">
756+
<b>Tip:</b> More driving = better accuracy. 15+ events per category is ideal.
757+
</div>
784758
</div>
785759
</div>
786760
</div>
@@ -857,7 +831,7 @@ const MIN_SPEED_KMH=7.2; // Must match mode.rs min_speed (2.0 m/s = 7.2 km/h)
857831
const EMA_ALPHA=0.35; // Must match mode.rs EMA alpha for accurate simulation
858832
859833
// State
860-
let scenario='guided';
834+
let scenario='neighborhood'; // Single calibration mode - drive normally
861835
let recording=false;
862836
let startTime=0;
863837
let lastSeq=0;
@@ -892,45 +866,6 @@ let gpsAccelCorr=[]; // correlation between GPS accel and velocity accel
892866
let centCorr=[]; // lat_g vs v*omega correlation
893867
let headingVsGyro=[]; // GPS heading rate vs gyro wz
894868
895-
// Scenario instructions
896-
const INSTRUCTIONS={
897-
guided:`<ol>
898-
<li>Find a safe road or empty parking lot</li>
899-
<li><b>Calibrate on level ground</b> - inclines affect accuracy!</li>
900-
<li>Press Start - events auto-detect as you drive</li>
901-
<li><b>Accelerate</b> firmly 5+ times from slow/stop</li>
902-
<li><b>Brake</b> normally 5+ times to slow/stop</li>
903-
<li><b>Turn</b> 10+ times at normal driving speed</li>
904-
<li>Drive as you <i>normally</i> would!</li>
905-
</ol>`,
906-
city:`<ol>
907-
<li>Drive around the block or a few city blocks</li>
908-
<li>Include: stop signs, traffic lights, turns</li>
909-
<li>5-10 loops captures enough events</li>
910-
<li>Normal city driving - no need to rush</li>
911-
</ol>`,
912-
highway:`<ol>
913-
<li>Merge onto highway, cruise at speed</li>
914-
<li>Make 3-5 lane changes</li>
915-
<li>Use on/off ramps for accel/brake</li>
916-
<li>One exit-and-re-enter cycle works</li>
917-
</ol>`,
918-
parking:`<ol>
919-
<li>Empty <b>flat</b> parking lot is ideal</li>
920-
<li>Start/stop on level ground (not inclines!)</li>
921-
<li>5 firm accelerations from stop</li>
922-
<li>5 firm (safe) braking stops</li>
923-
<li>Figure-8s or circles for turn data</li>
924-
<li>Cleanest calibration data!</li>
925-
</ol>`
926-
};
927-
928-
function selectScenario(s){
929-
scenario=s;
930-
document.querySelectorAll('.scenario').forEach(el=>el.classList.toggle('active',el.dataset.scenario===s));
931-
$('instructions-text').innerHTML=INSTRUCTIONS[s];
932-
}
933-
934869
function parsePacket(buf){
935870
const d=new DataView(buf);
936871
return{
@@ -1564,11 +1499,9 @@ function toggleRecording(){
15641499
15651500
btn.textContent='Stop Calibration';
15661501
btn.className='btn btn-start recording';
1567-
$('scenario-card').style.opacity='0.5';
15681502
}else{
15691503
btn.textContent='Start Calibration';
15701504
btn.className='btn btn-start';
1571-
$('scenario-card').style.opacity='1';
15721505
computeSuggestions();
15731506
$('report').textContent=generateReport();
15741507
$('report-card').style.display='block';
@@ -1582,7 +1515,6 @@ setInterval(()=>{
15821515
}
15831516
},1000);
15841517
1585-
document.querySelectorAll('.scenario').forEach(el=>el.onclick=()=>selectScenario(el.dataset.scenario));
15861518
$('btn-start').onclick=toggleRecording;
15871519
$('btn-apply').onclick=applySettings;
15881520
$('btn-export').onclick=exportReport;

0 commit comments

Comments
 (0)