IoT Sensor Simulator
End-to-end sensor pipeline · Python 3, SQLite, Docker
A virtual edge device that reads temperature and humidity, stores every
reading locally in SQLite, and ships it to a cloud ingest server over HTTP.
Two services, one docker compose up.
I wrote it as a stand-in for a physical Raspberry Pi sensor node. I did not have the hardware, and I did not want that to be the reason I never learned the flow an edge device actually performs: read, store, upload, repeat.
A real sensor is a small computer that measures something and posts the number somewhere. This is that computer, written in software. It invents plausible readings, writes them down in case the network is out, and sends them to a server that checks each one before accepting it.
What it does
The device. A virtual sensor whose readings drift between
measurements instead of jumping at random, so downstream code meets data that
behaves like the real thing. Every reading is written to SQLite with an
uploaded flag before any network call is attempted.
The upload. A real HTTP POST to a configurable endpoint, not a mock. Pointing the device at a managed cloud service means changing one environment variable, not changing code.
The server. A cloud-side ingest service that validates incoming JSON and answers with the status code the situation deserves: 200 for accepted, 400 for malformed JSON, 422 for JSON that parses but is missing fields, 404 for an unknown path.
Decisions worth defending
Store before sending. The reading is committed to SQLite first, and only then uploaded. A device that holds readings in memory until the network is ready loses them the moment it restarts, and an edge device restarts.
400 and 422 are different answers. Collapsing them into one error is easier and tells the caller nothing. Malformed JSON is the sender's syntax problem; missing fields is the sender's schema problem. They need different fixes, so they get different codes.
Shutdown is a feature. The simulator handles SIGINT and SIGTERM and closes the database on the way out, because a container that is killed mid-write leaves a mess that is tedious to reason about later.
What I would do next
The uploaded flag exists but nothing yet acts on it. The
obvious next piece is a retry queue that re-sends anything still marked
unsent, which is the point at which this stops being a demo and starts being
able to survive a bad network. After that: an API key on the ingest endpoint,
and a small dashboard over the stored readings.
My first version drew each reading independently at random. It ran, it uploaded, and the data was obviously fake: temperature jumped from 22 degrees to 9 and back to 30 in readings taken five seconds apart. Nothing downstream could be tested against it, because no sensor behaves that way. A pipeline fed on impossible data proves nothing about the pipeline.
The answer is drift — each reading starts from the previous one and moves by a small amount — and the small amount is where I lost the time. Too small and the line is flat, which is just as unrealistic in the other direction. Too large and the value wanders out of any plausible range within minutes. It also needed pulling back toward a sensible middle, because a number that only drifts will eventually leave the room no matter how small each step is.
I tuned that calculation more than I touched the HTTP layer, the database and the containers combined. It was the least impressive-sounding part of the project and the only part I had to reason about from scratch.
Where it was built
Developed on a self-hosted Ubuntu Server 26.04 (ARM64) VM, accessed over SSH and managed entirely from the terminal with no GUI. Installing the OS, configuring the network, and managing users and permissions were done step by step on purpose rather than skipped, because the point was the environment as much as the program.
Run it yourself
git clone https://github.com/KNOX80/iot-sensor-simulator
cd iot-sensor-simulator && docker compose up --buildThe sensor posts a reading every five seconds; the receiver logs each one as it arrives.