Industrial firmware blueprint for ESP32 heater control with deterministic PID and secure OTA.
FreeRTOS task isolation, HTTPS multi-source update pipeline, SHA-256 verification, rollback-safe boot.
Published by @dev-nicolasv
This repository provides a production-ready firmware foundation for industrial thermal control systems where deterministic control and safe remote lifecycle management are both mandatory.
System behavior is split into two strict execution domains:
- High-priority real-time PID task for heater actuation
- Independent OTA task for secure update checks and staged firmware download
The control loop never depends on network operations.
In industrial environments, a control function must keep timing guarantees even when network links degrade.
This design enforces:
- Core/task isolation between process control and OTA
- Deterministic loop pacing using
vTaskDelayUntil - OTA activation only after complete image integrity verification
- Rollback-safe boot validation through ESP32 dual-partition strategy
- OOP PID controller with anti-windup and bounded output
- 0-10V sensor acquisition path with ADC scaling and process normalization
- PWM heater output with fixed frequency and bounded duty command
- HTTPS-only OTA (Local Portal, AWS, ThingsBoard)
- SHA-256 streamed digest verification before boot partition switch
- Automatic rollback pathway for invalid post-update startup
- Task Watchdog (
esp_task_wdt) integration for control and OTA tasks - Source fallback order: Local Portal -> AWS -> ThingsBoard
flowchart LR
A["0-10V Sensor"] --> B["ADC + Scaling"]
B --> C["Control Task (PID, Core 1)"]
C --> D["PWM Heater"]
E["Local Portal HTTPS"] --> F["OTA Task (Core 0)"]
G["AWS HTTPS"] --> F
H["ThingsBoard HTTPS"] --> F
F --> I["Inactive OTA Slot"]
I --> J["SHA-256 Validation"]
J --> K["Set Boot Partition"]
K --> L["Reboot + Pending Verify"]
L --> M["Mark Valid or Rollback"]
Extended design notes: docs/ARCHITECTURE.md
- Physical sensor range:
0..10V - ESP32 ADC input: scaled to
0..3.3V - ADC pin default:
GPIO34
- PWM output pin default:
GPIO25 - PWM channel:
0 - PWM frequency:
2 kHz - PWM resolution:
10 bits
All defaults are centralized in include/AppConfig.h.
src/main.cpp: boot sequence, watchdog init, task startupsrc/ControlTask.cpp: deterministic PID control loopsrc/OtaTask.cpp: OTA background scheduling tasksrc/OtaManager.cpp: metadata fetch, download, hash validation, partition switchsrc/PIDController.cpp: object-oriented PID implementationinclude/AppConfig.h: compile-time runtime configurationpartitions.csv: factory + OTA A/B + otadata layouttools/generate_ota_manifest.py: OTA metadata generatortools/local_ota_portal.py: local HTTPS OTA server for plant/staging networks
Edit platformio.ini build flags:
WIFI_SSIDWIFI_PASSWORDOTA_LOCAL_METADATA_URLOTA_AWS_METADATA_URLOTA_THINGSBOARD_METADATA_URL
Optional auth headers per source are also supported.
- Copy
include/PrivateCertificates.h.exampletoinclude/PrivateCertificates.h - Add root CA PEM blocks for your endpoints
- Keep
include/PrivateCertificates.hprivate (already gitignored)
pio run
pio run -t upload
pio device monitor{
"version": "1.0.1",
"url": "https://ota.example.com/esp32/firmware-1.0.1.bin",
"sha256": "64_hex_chars_checksum",
"size": 340977
}{
"fw_version": "1.0.1",
"fw_url": "https://ota.example.com/esp32/firmware-1.0.1.bin",
"fw_checksum": "64_hex_chars_checksum",
"fw_size": 340977
}- Local Portal: docs/DEPLOYMENT_LOCAL_PORTAL.md
- AWS: docs/DEPLOYMENT_AWS.md
- ThingsBoard: docs/DEPLOYMENT_THINGSBOARD.md
Generate metadata from a built artifact:
python3 tools/generate_ota_manifest.py \
--firmware .pio/build/esp32dev/firmware.bin \
--version 1.0.1 \
--url https://your-host/firmware-1.0.1.bin \
--output release/metadata.jsonOperational guidance:
- OTA URLs must be
https:// - Firmware is activated only after SHA-256 match
- New image requires post-boot validation mark as
valid - Failed startup health check triggers rollback path
Pending project license definition.