
neha-priya-1171/sensor-telemetry-pipeline
API developmentA personal project by a 2nd-year ECE student demonstrating how structured prompt engineering can bridge embedded hardware to cloud backends — without writing a single line of server code from scratch.
Overview
A personal project by a 2nd-year ECE student demonstrating how structured prompt engineering can bridge embedded hardware to cloud backends — without writing a single line of server code from scratch. An ESP32 microcontroller reads 6-axis IMU sensor data, timestamps it using NTP, and sends it over Wi-Fi to a FastAPI server hosted on Replit — which validates the data and stores it in a SQLite database. Every packet returns HTTP 201 Created with a sequential record_id. The entire backend was — specifically applying the prompt engineering techniques from my coursework: role framing, iterative refinement, error-driven prompting, and schema anchoring. In Electronics & Communication Engineering, the gap between hardware and software is shrinking fast. Cyber-physical systems, IoT, and smart devices are increasingly central to the field — and they all require this exact stack: a microcontroller producing data, a network layer transporting it, and a backend storing it.
README
ESP32 IoT Telemetry Pipeline
Built entirely with AI — no prior backend experience required.
A personal project by a 2nd-year ECE student demonstrating how structured prompt engineering can bridge embedded hardware to cloud backends — without writing a single line of server code from scratch.
What is this?
An ESP32 microcontroller reads 6-axis IMU sensor data, timestamps it using NTP, and sends it over Wi-Fi to a FastAPI server hosted on Replit — which validates the data and stores it in a SQLite database. Every packet returns HTTP 201 Created with a sequential record_id.
The entire backend was scaffolded, debugged, and deployed using AI — specifically applying the prompt engineering techniques from my coursework: role framing, iterative refinement, error-driven prompting, and schema anchoring.
Why this matters for ECE
In Electronics & Communication Engineering, the gap between hardware and software is shrinking fast. Cyber-physical systems, IoT, and smart devices are increasingly central to the field — and they all require this exact stack: a microcontroller producing data, a network layer transporting it, and a backend storing it.
This project proves you don’t need a full software engineering background to build that pipeline. You need to know what to ask for and how to read the errors.
System Architecture
ESP32 Dev Module
│
├── Reads IMU (accel_x/y/z, gyro_x/y/z)
├── Syncs clock via NTP → pool.ntp.org
├── Builds JSON payload with 13-digit epoch timestamp
└── HTTP POST ──► /api/v1/telemetry
│
├── X-API-Key header check
├── Pydantic schema validation
├── Replay attack timestamp check
└── SQLite INSERT → returns record_id
sequenceDiagram
participant ESP32
participant NTP as pool.ntp.org
participant API as FastAPI (Replit)
participant DB as SQLite
ESP32->>NTP: Sync clock on boot
NTP-->>ESP32: Unix epoch (ms)
loop Every 5 seconds
ESP32->>API: POST /api/v1/telemetry + X-API-Key
API->>API: Validate key → schema → timestamp
API->>DB: INSERT record
DB-->>API: record_id
API-->>ESP32: 201 Created {record_id: 286}
end
The Payload Schema
This is the exact JSON structure the Pydantic model expects. Any deviation returns HTTP 422.
{
"device_id": "esp32-node-01",
"firmware_version": "1.0.0",
"timestamp_ms": 1781670054657,
"imu": {
"accel_x": 0.0412,
"accel_y": -0.1203,
"accel_z": 9.7814,
"gyro_x": 0.0023,
"gyro_y": -0.0011,
"gyro_z": 0.0005
}
}
Key constraint:
timestamp_msmust be a real 13-digit Unix epoch in milliseconds — notmillis()uptime. The server rejects anything that looks like a replayed/stale packet.
The Debugging Journey (404 → 422 → 201)
This is where the real learning happened. Every HTTP error was treated as a prompt input, not a failure.
| Error | Root Cause | Fix |
|---|---|---|
| Flash timeout | Baud rate mismatch in Arduino IDE | Set upload speed to 115200 |
| HTTP 404 | ESP32 was hitting / instead of the actual route |
Inspected /docs Swagger UI → found prefix /api/v1/telemetry |
| HTTP 422 — missing field | timestamp_ms absent from payload |
Added field to JSON builder |
| HTTP 422 — replay rejected | millis() uptime ≠ real-world time |
Implemented NTP sync with configTime() + pool.ntp.org |
| HTTP 422 — schema mismatch | Sent accelerometer{} + gyroscope{} as two objects |
Flattened to single imu{} with accel_x/y/z, gyro_x/y/z |
| HTTP 201 ✓ | Everything aligned | Sequential record_id streaming confirmed |
Serial Monitor — Final State:
HTTP Response code: 201
{"status":"accepted","record_id":281,...}
HTTP Response code: 201
{"status":"accepted","record_id":282,...}
...
HTTP Response code: 201
{"status":"accepted","record_id":286,...}
NTP Time Sync — Why It Matters
The backend implements replay attack protection by comparing timestamp_ms against the server’s real-world clock. Using millis() (ESP32 uptime since boot, e.g. 4823 ms) gets flagged as a stale/spoofed packet.
The fix: sync to NTP on boot, then generate genuine epoch timestamps.
// Sync on startup
configTime(0, 0, "pool.ntp.org");
// Generate 13-digit epoch ms for every packet
unsigned long long getEpochMs() {
struct timeval tv;
gettimeofday(&tv, nullptr);
return ((unsigned long long)tv.tv_sec * 1000ULL) +
((unsigned long long)tv.tv_usec / 1000ULL);
}
How Prompt Engineering Built This
This is the part I’m most proud of — the backend wasn’t typed manually. It was described, generated, and refined using AI, following the methodology from my prompt engineering coursework.
The workflow: Describe → Generate → Refine
The session taught that even a rough prompt gets you 80% of the way. The remaining 20% comes from iteration — not rewriting from scratch, but building on the existing output with targeted follow-ups.
Example prompt that scaffolded the backend:
You are a senior backend engineer.
Task: Generate a FastAPI project structure.
Requirements:
- Python 3.11
- POST endpoint for IoT telemetry
- Pydantic models for IMU/GPS/power data
- X-API-Key authentication
- SQLite storage
- Input validation & error handling
Output: Folder structure + working code files
That one prompt — with a clear role, task, requirements, and output format — generated the entire backend skeleton.
Error-driven prompting
Every time the ESP32 returned an error, I pasted the exact HTTP response into the AI conversation:
The ESP32 is returning:
HTTP Response code: 422
{"detail":[{"type":"missing","loc":["body","timestamp_ms"],"msg":"Field required"}]}
Here is my current firmware JSON builder: [code]
What needs to change?
This is the core technique: treat error logs as diagnostic prompts, not failure messages.
Schema anchoring
For the structural mismatch (two nested objects vs one flat imu{}), I opened the Swagger UI at /docs, read the exact Pydantic field tree, and pasted it directly into the prompt. The AI then generated exactly conforming firmware code.
Takeaway from the course: “The more specific you are, the more useful the output — but even a rough prompt gets you 80% of the way there.” This project proves it. The specificity came from the error logs and the Swagger schema — not from prior backend knowledge.
Tools Used
| Tool | What it did |
|---|---|
| Claude | Backend scaffolding, firmware architecture, NTP sync design, error diagnosis |
| Replit Agent (Gemini) | Structured logging, stats endpoint, deployment pipeline |
Swagger UI (/docs) |
Live schema validation — the ground truth for aligning firmware payloads |
| Arduino IDE 2.3 | Firmware compilation, flashing, Serial Monitor debugging |
Recommended workflow from the course: Claude to generate → Replit to run. That’s exactly what happened here.
Setup
Backend is live at:
https://suburban-darkorange-type--priyassnh.replit.app/docs
Firmware — configure these in config.h before flashing:
#define WIFI_SSID "your-network"
#define WIFI_PASSWORD "your-password"
#define SERVER_URL "https://suburban-darkorange-type--priyassnh.replit.app"
#define API_KEY "dev-secret"
Then: Tools > Board > ESP32 Dev Module · Tools > Upload Speed > 115200 · Upload.
What’s Next
- [ ] Add GPS and battery telemetry fields
- [ ] Replace hardcoded API key with
.envsecret management - [ ] Build a simple dashboard to visualize the incoming
record_idstream - [ ] Add offline buffering on the ESP32 (queue to flash, flush on reconnect)
ECE 2nd Year · Prompt Engineering coursework — GITAA / IIT Madras Zanzibar · Built with Claude + Replit
Recommended Tools
Try a different keyword or remove a filter.
Install
npx skillfish add neha-priya-1171/sensor-telemetry-pipeline