September 29, 2026
ESP32 temperature inference — proving the model/firmware contract
Connected Python model training and export with a real ESP32 inference path and Flask reconstruction, including a fixed scaler and a two-value latent vector.
Personal prototype
- Role
- Software engineer
- Published
- September 2026
- Focus
- Personal prototype
- Engineer
- Saaim Abdullah
Getting a model onto a microcontroller is a systems problem
What the implementation contains
| Engineering fact | Value in this variant | Why it matters |
|---|---|---|
| Sensor | DHT11 | Physical device input, not just an offline CSV |
| Scalar sensor value used | 1 temperature reading | Defines the shape of the model's input |
| Latent vector width | 2 values | Defines the device-to-Flask interface |
| Model deployment runtimes | 2 — ESP32 and Python service | Export/decoder compatibility must be maintained |
| Decode API | /decode | HTTP boundary for reconstructed inference |
| Firmware model runtime | TensorFlow Lite Micro | Embedded tensor arena and operator registration |
| Server runtime | Flask with saved decoder/scaler | Inverse transform and output handling |
End-to-end execution path
| Step | Where it happens | What I engineered |
|---|---|---|
| 1. Create data | Python training script | Synthetic temperature examples for repeatable training |
| 2. Scale values | Training pipeline | Establish fixed numerical transform expected by both runtimes |
| 3. Train | Keras | Learn encoder and compatible decoder weights |
| 4. Export | Python / TFLite tooling | Generate device model file and C header |
| 5. Read input | ESP32 firmware | Sample temperature from DHT11 |
| 6. Encode | TFLite Micro | Run model within allocated tensor arena |
| 7. Send | ESP32 network client | Serialize two-value vector as JSON |
| 8. Decode | Flask /decode | Check vector shape, predict and inverse-scale temperature |
| 9. Respond | Flask API | Return success response; currently does not expose numeric reconstruction |
Training and artifact packaging
On-device inference
Reconstruction and contract checking
The difficult engineering decisions
| Decision | Reason | Risk addressed |
|---|---|---|
| Keep preprocessing constants aligned | Different scales produce incorrect outputs | Silent model drift across environments |
| Export a compact firmware-compatible model | Embedded runtime supports fewer operations | Model initialization failure |
| Use a fixed tensor arena | Predictable bounded memory allocation | Runtime memory pressure |
| Validate a 2-value payload | Server must know the latent shape | Invalid decoder inputs |
| Separate device encoder and server decoder | Experiments with split inference | Full model need not run on microcontroller |
| Track artifacts as a single release | Interdependent versions must match | Firmware/server compatibility errors |
How I would test it as an engineering system
- Parity tests: run the same input through the Python encoder and the ESP32-compiled model; compare outputs within a documented tolerance.
- Schema tests: reject a zero-length, one-value, or three-value latent request when two values are required.
- Range tests: feed temperatures at the boundaries of the fitted scaler and beyond its intended range.
- Hardware tests: record inference time, tensor arena utilization, wireless retry behavior, and failure recovery on a named ESP32 board.
- Model-quality tests: compare original sensor values against reconstructed values and report MAE/RMSE on held-out real readings.


SaaimOpen to full-time roles, contract work, and conversations about things worth building.