NP

neha-priya-1171/sensor-telemetry-pipeline

API development
0 stars Quality 39 Trend 8

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.

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_ms must be a real 13-digit Unix epoch in milliseconds — not millis() 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 .env secret management
  • [ ] Build a simple dashboard to visualize the incoming record_id stream
  • [ ] 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

View this README on GitHub

Recommended Tools

Try a different keyword or remove a filter.

Install

npx skillfish add neha-priya-1171/sensor-telemetry-pipeline