Data Collection Layer for PULSE 2.0 AMD Slingshot Hackathon — Smartphone Sensor Pipeline
📱 PULSE Collector (React Native / Expo)
│
├── 5 Sensor Channels (simultaneous)
│ ├── Accelerometer + Gyroscope @ 200Hz → IMU packets
│ ├── GPS @ 1Hz → GPS packets
│ ├── Camera (rear) @ 2fps → FRAME packets (base64 JPEG)
│ └── Microphone @ 10Hz (RMS) → AUDIO packets
│
├── Transport
│ ├── WebSocket → ws://<laptop>:8000/ws/<session_id>
│ └── SQLite offline buffer (auto-drain on reconnect)
│
└── On-device IRI
└── Rolling RMS estimate for live display (full computation on backend)
pulse-collector/
├── App.jsx ← Navigation root
├── app.json ← Expo config + permissions
├── package.json
│
├── screens/
│ ├── SetupScreen.jsx ← Server config, session name, camera height
│ ├── RecordingScreen.jsx ← MAIN: live sensor streams + record button
│ └── HistoryScreen.jsx ← Past sessions, export, delete
│
├── hooks/
│ ├── useIMU.js ← Accelerometer + Gyroscope @ 200Hz
│ ├── useGPS.js ← Location watcher + distance tracking
│ ├── useCamera.js ← Rear camera frame capture @ 2fps
│ └── useAudio.js ← Microphone RMS via expo-av metering
│
├── services/
│ ├── WebSocketClient.js ← WS connect/reconnect/queue singleton
│ └── OfflineBuffer.js ← SQLite sessions + packet + segment tables
│
├── components/
│ ├── SensorStatusBar.js ← 5-dot sensor status indicators
│ ├── AccelWaveform.js ← SVG sparkline of accel Z
│ ├── IRIGauge.js ← Large IRI number with condition badge
│ └── SegmentHistory.js ← Horizontal chips of completed 100m segments
│
└── utils/
├── theme.js ← Colors, spacing, IRI color helpers
└── iriEstimate.js ← Rolling RMS IRI + haversine distance
cd pulse-collector
npm installnpx expo startScan QR code with Expo Go app (iOS/Android).
For production build:
npx expo run:ios
npx expo run:androidOn your laptop:
cd pulse/backend
uvicorn main:app --host 0.0.0.0 --port 8000Both devices must be on the same WiFi network.
All packets are JSON objects sent over ws://<host>/ws/<session_id>.
{
"type": "IMU",
"timestamp": 1709123456789,
"ax": 0.12,
"ay": -0.03,
"az": 9.83,
"rx": 0.01,
"ry": 0.00,
"rz": 0.02
}ax/ay/az in m/s² · rx/ry/rz in rad/s
{
"type": "GPS",
"timestamp": 1709123456790,
"lat": 12.34567,
"lng": 78.90123,
"speed_ms": 8.33,
"speed_kmh": 30.0,
"accuracy_m": 4.2,
"heading": 270.0,
"distance_m": 450.2
}{
"type": "FRAME",
"timestamp": 1709123456800,
"data": "/9j/4AAQSkZJRgAB...",
"width": 640,
"height": 480
}data is base64-encoded JPEG
{
"type": "AUDIO",
"timestamp": 1709123456850,
"rms": 0.042,
"dbfs": -27.5,
"sample_rate": 44100
}{
"type": "SEGMENT_COMPLETE",
"segment": {
"segment_id": "12.3456,78.9012",
"iri_value": 4.8,
"iri_condition": "Poor",
"rut_depth_mm": 23.1,
"surface_type": "WBM",
"final_condition": "Poor",
"gps": { "lat": 12.3456, "lng": 78.9012 }
}
}- Mount phone rigidly — windshield suction mount or dashboard holder
- Rear camera facing road — unobstructed view of road surface
- Measure camera height — from phone lens to road surface (typically 1.1–1.4m in a car)
- Enter camera height in Setup screen before starting
- Drive ≥ 20 km/h for valid IRI readings
| IRI (m/km) | Condition | Action |
|---|---|---|
| < 2.0 | Good | Routine Maintenance |
| 2.0 – 4.0 | Fair | Preventive Treatment |
| 4.0 – 6.0 | Poor | Rehabilitation |
| > 6.0 | Very Poor | Reconstruction |
| Permission | Platform | Purpose |
|---|---|---|
| Motion/Accelerometer | iOS (NSMotionUsageDescription) | IRI computation at 200Hz |
| Location | iOS + Android | GPS speed + segment geotag |
| Camera | iOS + Android | Visual road surface assessment |
| Microphone | iOS + Android | Acoustic surface classification |
| Wake Lock | Android | Keep screen on during recording |
When the backend is unreachable:
- IMU/GPS/Audio packets are queued in memory (up to 500 packets)
- Session metadata and segment results are always saved to SQLite
- On reconnection, queued packets are drained to backend automatically
- Session history is always accessible offline
PULSE 2.0 — AMD Slingshot Hackathon, February 2026