Contents — 26 sections
Project Overview
Records temperature and humidity across a warehouse to a tamper-evident, audit-ready log — proving storage stayed in spec and warning before it drifts out.
Many goods — pharmaceuticals, food, electronics, documents, museum pieces — must be stored within a temperature and humidity range, and the person storing them must be able to prove the conditions were maintained. A regulator, a customer, or an insurer will not take "it was fine" on trust; they want a continuous, trustworthy record. Yet warehouses are large and their climate varies from the loading dock to the back corner to the roof space, so a single wall thermometer proves nothing. This project logs temperature and humidity at multiple points across a warehouse to a tamper-evident, audit-ready record — proving storage stayed in spec, and alerting before it drifts out of it.
It places sensors where the warehouse is actually at risk — near doors, in the warmest and coldest corners, high and low (warm air rises), in the product zones that matter — because compliance is judged on the worst conditions goods experienced, not a flattering average. It timestamps every reading against a real-time clock, writes it to an append-only log, and mirrors it off-device, so the record is trustworthy and survives. Alongside temperature and humidity it derives the metrics compliance actually uses: excursions (any time-out-of-range, with duration and peak), mean kinetic temperature for temperature-sensitive goods, and dew point where condensation risk matters.
Beyond proving the past, it protects the present: it alarms the moment a zone drifts toward its limit — a door left open, HVAC failing, a humid spell — so someone can act before goods are damaged, and it runs on battery backup and local logging so a power cut (exactly when incidents happen) never leaves a gap. It exports the audit trail buyers and inspectors require. It is honest that a DIY logger is not automatically a certified/validated compliance instrument where regulations demand one (pharma GDP, for instance, has specific requirements), and that sensor placement and calibration decide everything. But as a multi-point, tamper-evident, alerting climate logger, it does exactly what regulated storage needs: it keeps an honest, defensible record that conditions stayed in spec, and warns in time when they might not.
What this project does
- Logs temperature and humidity at multiple warehouse points
- Places sensors at the worst/most-variable locations, not one average spot
- Timestamps to a tamper-evident, append-only log, mirrored off-device
- Derives excursions (duration/peak), mean kinetic temperature and dew point
- Alarms before a zone drifts out of range
- Rides power/network outages on battery + local logging
- Exports an audit-ready trail for inspectors/customers
Real-World Applications
| Setting | How it is used |
|---|---|
| Pharma / GDP storage | Temperature/humidity compliance logging with excursions and MKT for medicine warehouses (supplementary to validated systems). |
| Food / FMCG warehousing | Proving ambient storage conditions and alerting before spoilage risk. |
| Electronics / documents / archives | Humidity/temperature control and audit for moisture- or heat-sensitive goods. |
| Museums / sensitive storage | Stable-climate monitoring with dew-point and excursion records. |
Deployment contexts where a build of this kind earns its keep.
Features & Capabilities
- Multi-point sensing representing the whole warehouse
- Tamper-evident, timestamped compliance log (local + mirror)
- MKT, excursion and dew-point metrics
- Proactive alerts before goods are at risk
- Outage ride-through (battery + local log)
- Exportable audit trail
- Honest scope vs certified/validated instruments
Difficulty, Time & Required Skills
| Attribute | Value |
|---|---|
| Difficulty level | Intermediate |
| Estimated completion time | 10–16 hours |
| Indicative build cost | ₹3,000 – ₹4,800 |
| Primary discipline | Industrial |
| Reference platform | ESP32 DevKit V1 (ESP-WROOM-32) |
Skills you should have (or will pick up)
- Multi-point temperature/humidity sensing and placement
- Tamper-evident logging with a real-time clock and off-device mirror
- Excursion, MKT and dew-point computation
- Proactive alerting and outage ride-through
- Audit-trail export and compliance framing
Bill of Materials
Every part below is commonly available from Indian and international hobby-electronics suppliers. Prices are indicative 2026 retail figures in Indian rupees and will drift — treat them as a budgeting guide, not a quotation.
| Component | Key specification | Qty | Approx. cost |
|---|---|---|---|
| ESP32 DevKit V1 (ESP-WROOM-32) Wi-Fi transmit bursts peak near 500 mA — size the regulator accordingly. | Dual-core Xtensa LX6 @ 240 MHz, 520 KB SRAM, 4 MB flash, Wi-Fi 802.11 b/g/n + BLE 4.2, 34 GPIO, 18× 12-bit ADC, 2× 8-bit DAC | 1 | ₹450 |
| SHT31-D temperature + humidity sensor The built-in heater burns off condensation — essential for outdoor or greenhouse use. | −40 to +125 °C ±0.2 °C, 0–100 %RH ±2 %, 1 Hz–10 Hz, on-chip heater | 4 | ₹2,480 |
| DHT22 / AM2302 temperature + humidity sensor Needs a 4.7 kΩ pull-up on the data line and 2 s between reads. | −40 to +80 °C ±0.5 °C, 0–100 %RH ±2 %, 0.5 Hz sample rate, single-wire digital | 1 | ₹250 |
| Reed switch + magnet pair (door contact) Wire it normally-closed so a cut cable reads the same as an open door — fail-safe by construction. | NO contact, 10–15 mm operating gap, 100 V / 0.5 A, > 10⁷ operations | 1 | ₹55 |
| DS3231 precision RTC + CR2032 backup Vastly better than DS1307; some boards trickle-charge a non-rechargeable cell — cut that resistor. | ±2 ppm (±1 min/year), temperature-compensated crystal, alarms, 32 kHz out | 1 | ₹180 |
| microSD card 32 GB A1 class For 24/7 loggers buy a high-endurance card — normal cards die in months. | A1 rated, 10 MB/s random write, UHS-I, endurance-grade recommended | 1 | ₹450 |
| Active piezo buzzer 5 V Active buzzers make tone on DC; passive ones need a PWM carrier. | 85 dB at 10 cm, 2.3 kHz resonance, 12 mm diameter | 1 | ₹25 |
| 18650 Li-ion cell 3400 mAh + holder Never charge below 0 °C; always use a protected cell or a BMS. | 3.7 V nominal, 4.2 V full, 3400 mAh, ~12.6 Wh, 2 C discharge | 1 | ₹450 |
| Multi-point T/RH sensors Placement at worst points is critical | SHT31 (accurate) at door/corner/high/low/product zones | 5 | ₹4,500 |
| Door sensors | Reed contacts logging door openings (a common excursion cause) | 2 | ₹400 |
| Backup battery + RTC | 18650 backup and DS3231 RTC for outage ride-through and accurate time | 1 | ₹300 |
| Mirror/host Use existing infrastructure | Server/cloud for the off-device audit trail | 1 | — |
Estimated total: ₹9,540, excluding tools, shipping and consumables.
Tools and consumables
- Soldering iron (temperature controlled, 350 °C) with 0.8 mm 60/40 or lead-free solder
- Digital multimeter — continuity, DC volts and current ranges
- Wire strippers, flush cutters and a small set of precision screwdrivers
- Heat-shrink tubing and a heat gun (or a lighter, carefully)
- A laptop with a USB port and the toolchain listed above
Hardware Specifications
| Part | Specification | Supply | Interface | Reference |
|---|---|---|---|---|
| ESP32 DevKit V1 (ESP-WROOM-32) | Dual-core Xtensa LX6 @ 240 MHz, 520 KB SRAM, 4 MB flash, Wi-Fi 802.11 b/g/n + BLE 4.2, 34 GPIO, 18× 12-bit ADC, 2× 8-bit DAC | 3.3 V logic / 5 V USB | UART, SPI, I²C, I²S, CAN, PWM | Datasheet |
| SHT31-D temperature + humidity sensor | −40 to +125 °C ±0.2 °C, 0–100 %RH ±2 %, 1 Hz–10 Hz, on-chip heater | 2.4–5.5 V | I²C (0x44/0x45) | Datasheet |
| DHT22 / AM2302 temperature + humidity sensor | −40 to +80 °C ±0.5 °C, 0–100 %RH ±2 %, 0.5 Hz sample rate, single-wire digital | 3.3–6 V | 1-wire proprietary | Datasheet |
| Reed switch + magnet pair (door contact) | NO contact, 10–15 mm operating gap, 100 V / 0.5 A, > 10⁷ operations | passive | Digital with pull-up | Datasheet |
| DS3231 precision RTC + CR2032 backup | ±2 ppm (±1 min/year), temperature-compensated crystal, alarms, 32 kHz out | 2.3–5.5 V | I²C (0x68) | Datasheet |
| microSD card 32 GB A1 class | A1 rated, 10 MB/s random write, UHS-I, endurance-grade recommended | 3.3 V | SDIO / SPI | Datasheet |
| Active piezo buzzer 5 V | 85 dB at 10 cm, 2.3 kHz resonance, 12 mm diameter | 3–5 V | Digital / PWM | Datasheet |
| 18650 Li-ion cell 3400 mAh + holder | 3.7 V nominal, 4.2 V full, 3400 mAh, ~12.6 Wh, 2 C discharge | 3.0–4.2 V | Holder / spot-welded tabs | Datasheet |
Consolidated electrical and interface specifications for every active part in the build.
Power Budget & Supply Sizing
Add up the typical active current of every part, then size the supply with at least 50 % headroom so transmit bursts and motor inrush never brown out the controller.
| Load | Supply rail | Typical current (mA) | Notes |
|---|---|---|---|
| ESP32 DevKit V1 (ESP-WROOM-32) | 3.3 V logic / 5 V USB | 160 | Wi-Fi transmit bursts peak near 500 mA — size the regulator accordingly. |
| SHT31-D temperature + humidity sensor | 2.4–5.5 V | 6 | The built-in heater burns off condensation — essential for outdoor or greenhouse use. |
| DHT22 / AM2302 temperature + humidity sensor | 3.3–6 V | 1.5 | Needs a 4.7 kΩ pull-up on the data line and 2 s between reads. |
| DS3231 precision RTC + CR2032 backup | 2.3–5.5 V | 0.2 | Vastly better than DS1307; some boards trickle-charge a non-rechargeable cell — cut that resistor. |
| microSD card 32 GB A1 class | 3.3 V | 100 | For 24/7 loggers buy a high-endurance card — normal cards die in months. |
| Active piezo buzzer 5 V | 3–5 V | 30 | Active buzzers make tone on DC; passive ones need a PWM carrier. |
Summed typical draw is 297.7 mA. With a 1.5× design margin the supply should deliver at least 500 mA continuously at the stated rail voltage.
Software Requirements & Development Environment
Reference toolchain: Arduino IDE 2.3.x with the ESP32 board package 3.x (or PlatformIO on VS Code). Anything newer normally works; anything older may lack the board definitions used here.
- Install the Arduino IDE 2.3.x (or PlatformIO if you prefer a real editor and dependency locking).
- Add
https://espressif.github.io/arduino-esp32/package_esp32_index.jsonunder File → Preferences → Additional Board Manager URLs, then install esp32 from the Boards Manager. - Set the correct port under Tools → Port. On Linux add yourself to the
dialoutgroup:sudo usermod -aG dialout $USERand log out and back in. - Open the Serial Monitor at 115200 baud — every sketch here logs its state there.
- Keep File → Preferences → Show verbose output during: compilation switched on while you are debugging build errors.
Required libraries
| Library | Why it is needed | Install |
|---|---|---|
| WiFi (ESP32 core) bundled | Station/AP connection management for the ESP32. | Bundled with the ESP32 Arduino core |
| PubSubClient 2.8 | Lightweight MQTT 3.1.1 client for constrained devices. | Library Manager → "PubSubClient" by Nick O'Leary |
| Adafruit BME280 2.2.x | Compensation maths for the Bosch pressure/humidity/temperature sensor. | Library Manager → "Adafruit BME280 Library" |
| Adafruit Unified Sensor 1.1.x | Common sensor event abstraction; a dependency of most Adafruit drivers. | Library Manager → "Adafruit Unified Sensor" |
| NTPClient / configTime bundled | Wall-clock time from an NTP server for timestamping. | Bundled (`configTime()` on ESP32) |
| SQLite 3.45+ | Zero-configuration embedded database for local logs. | Bundled with Python (`import sqlite3`) |
| Preferences (NVS) bundled | Wear-levelled key/value storage in ESP32 flash for settings. | Bundled with the ESP32 core |
Block Diagram
The block diagram shows the functional decomposition of the system — what senses, what decides, what acts, and where the data ends up.
Circuit Diagram & Wiring
Every signal line in the build is shown below, followed by a pin-by-pin connection table you can work through with a multimeter in hand.
| Peripheral | Peripheral pin | Controller pin | Signal |
|---|---|---|---|
| SHT31 ×N | SDA/SCL | GPIO 21/22 (+mux) | Zone temp/humidity (I²C) |
| Door reeds | NC | GPIO 34/35 | Door open/close |
| DS3231 RTC | SDA/SCL | GPIO 21/22 | Timestamps |
| microSD | SPI | GPIO 18/19/23/5 | Append-only local log |
| Buzzer | IN | GPIO 13 | Local alarm |
| Wi-Fi | on-chip | — | Off-device mirror |
| Backup battery | sense | ADC | Power supervision |
Wire one row at a time and tick it off — most "it does not work" reports trace back to a single swapped pair.
Wiring explanation
- Place accurate T/RH sensors at the warehouse's worst/most-variable points (near doors, warm/cold corners, high and low, product zones), not a single convenient wall.
- Use an I²C mux or addressable sensors for multiple SHT31s; map each to its location.
- Give the RTC a coin-cell backup for accurate timestamps, and log door openings (a common excursion cause).
- Power from mains with battery backup and sense the supply so outages are ridden through and logged.
- Mirror the append-only log off-device so the record survives tampering or loss.
System Architecture
Read the stack from the bottom up: physical hardware, the firmware that drives it, the transport that moves data off the device, and the software a human actually looks at.
Working Principle
A compliance climate logger exists to answer one question convincingly: did storage stay within spec, and can you prove it? "Prove" is the operative word, because in regulated or contractual storage, an unprovable claim is treated as a failure — a customer or inspector who cannot see a trustworthy record must assume the worst. So the logger's design centres on producing a record that is representative (of the whole warehouse, not one flattering spot), trustworthy (tamper-evident and timestamped), complete (no gaps, even through outages), and audit-ready (exportable in a form an inspector accepts).
Being representative means multi-point sensing at the worst locations. A warehouse is not one temperature: it is warm near the roof and the loading dock, cool in a shaded back corner, humid near a leaky door, and the goods in each zone experience their local conditions. Compliance is judged on the worst conditions any product experienced, so a single sensor — especially one placed somewhere convenient and benign — proves nothing and can hide a real excursion. The logger therefore places sensors deliberately at the door, the warmest and coldest corners, high and low, and in the product zones, so the record reflects the true envelope of conditions, and it logs door openings because an open door is the commonest cause of a local excursion.
Being trustworthy and complete mirrors the discipline of any compliance record. Every reading is timestamped against a backed-up real-time clock and written to append-only storage (you can add, not silently rewrite), with a sequence/hash so a deleted or altered entry is detectable, and the log is mirrored off-device so it survives tampering or loss of the unit. And because power and network fail precisely during the incidents you most need to document, the logger runs on battery backup and keeps logging locally through outages, syncing the backlog on recovery — a compliance logger that goes blind during a power cut is blind during the very event it exists to record.
On top of the raw log, the logger computes the metrics compliance actually uses, so the record is not just data but evidence. An excursion is any continuous period out of range, characterised by its start, duration and peak — because how far and how long matter, not merely that it happened. Mean kinetic temperature summarises a fluctuating temperature history into the single effective temperature the goods experienced, weighting warm periods more heavily (the standard the regulated cold/ambient chains use precisely because degradation accelerates with temperature). Dew point flags condensation risk where humidity control matters. And beyond documenting the past, the logger protects the present with proactive alarms the moment a zone drifts toward its limit, so a person can act — close the door, fix the HVAC — before goods are damaged, not after. The design is candid that a homebrew logger is not automatically a certified/validated instrument where regulations (e.g. pharmaceutical GDP) demand specific, qualified equipment, and that sensor calibration and placement bound its accuracy. But within that honest frame, it delivers exactly what regulated storage needs: an honest, representative, tamper-evident, complete and audit-ready record that conditions stayed in spec — and a timely warning when they might not.
The maths behind it
Mean kinetic temperature
MKT = (ΔH/R) / −ln[ (Σ e^(−ΔH/(R·T_i))) / n ]
T_i absolute temps (K) of each interval,
ΔH ≈ 83 kJ/mol, R = 8.314 J/mol·K
Warm periods dominate the average (degradation is
exponential in T) — the standard for temp-sensitive goods.
Excursion + dew point
Excursion (per representative zone vs limits):
in_excursion while T/RH out of range
record start, end, duration, peak
Dew point from T, RH (Magnus):
γ = ln(RH/100) + a·T/(b+T)
Td = b·γ/(a−γ)
condensation risk if surfaces near/below Td.
Representative (worst-case) evaluation
Judge compliance on the worst zone:
T_rep = max over zones (for over-temp)
RH_rep = max over zones (for humidity)
A single benign sensor hides excursions the goods actually
saw — use the worst point, not an average.
Program Flowchart
The firmware is a single cooperative loop. Nothing blocks for long, so networking, sensing and the user interface all stay responsive.
Assembly Instructions
Build on a breadboard first and only commit to solder once the whole system has run for an hour without a fault.
Place sensors at the worst points and set up time
Mount accurate T/RH sensors at the door, warm/cold corners, high/low and product zones, mapped to locations; add door sensors. Set up the RTC with coin-cell backup for accurate timestamps.
Set up tamper-evident logging
Define an append-only, sequence-numbered log to local storage, mirrored off-device, and run on mains with battery backup so outages do not create gaps.
Compute metrics and alerts
Compute excursions, MKT and dew point, and configure proactive alarms before a zone drifts out of range, plus audit export.
Step-by-Step Implementation Guide
Work through these in order. Each step ends in something you can observe, so a failure is always localised to the step you just finished.
Log representatively and compute metrics
Each interval, read all zones (and doors), append timestamped records, evaluate the worst zone against limits for excursions, and update MKT and dew point.
cppclimate-compliance.inostruct MKT { double sumExp=0; uint32_t n=0; } mkt; const double DH_R = 83000.0/8.314; void mktAdd(float tC){ mkt.sumExp += exp(-DH_R/(tC+273.15)); mkt.n++; } float mktValue(){ return mkt.n? (float)(DH_R/-log(mkt.sumExp/mkt.n)-273.15):NAN; } float dewPoint(float t, float rh){ const float a=17.27f, b=237.7f; float g = logf(rh/100.0f) + a*t/(b+t); return b*g/(a-g); } struct Excursion { bool active=false; uint32_t start=0; float peak=-99; } exc; void evaluate(float *zoneT, int n, float tLimit, uint32_t now){ float tRep = -99; for(int i=0;i<n;i++) if(zoneT[i]>tRep) tRep=zoneT[i]; // worst mktAdd(tRep); if (tRep > tLimit){ if (!exc.active){ exc.active=true; exc.start=now; exc.peak=tRep; } if (tRep>exc.peak) exc.peak=tRep; if (now-exc.start > GRACE) raiseAlarm("temp excursion", tRep, exc.peak); } else if (exc.active){ logExcursion(exc.start, now, exc.peak); // start/end/duration/peak exc.active=false; exc.peak=-99; } }float tRep = -99; for(int i=0;i<n;i++) if(zoneT[i]>tRep) tRep=zoneT[i]The worst (warmest) zone represents the warehouse, because compliance is judged on the worst conditions goods experienced, not an average that hides them.void mktAdd(float tC)Each interval folds into the running mean kinetic temperature, weighting warm periods exponentially as the standard requires.float dewPoint(float t, float rh)Dew point is derived so condensation risk can be flagged where humidity control matters.if (now-exc.start > GRACE) raiseAlarmAn out-of-range condition sustained past a grace period alarms, and the full excursion (start/end/duration/peak) is logged when it recovers — the evidence compliance needs.Log tamper-evidently, alert and export
Append each record with a sequence number/timestamp to local storage and mirror off-device, alarm proactively before limits, ride outages on battery, and provide audit export.
Complete Source Code
The listing below is complete and compiles as written — there are no elided sections. Read the annotations under each block before you upload it.
/* ═══════════════════════════════════════════════════════════════
Warehouse Climate Logger — ESP32, multi-point, tamper-evident
Logs T/RH at multiple worst-case points to an append-only,
timestamped, mirrored record; computes excursions, MKT and dew
point; alarms before drift-out; rides outages on battery + SD.
Supplementary to certified/validated compliance systems.
══════════════════════════════════════════════════════════════════ */
#include <WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <Adafruit_SHT31.h>
#include <RTClib.h>
#include <SD.h>
#include <SPI.h>
#include <math.h>
#define NZONES 5
#define SD_CS 5
#define PIN_BUZZER 13
#define T_LIMIT 25.0f
#define RH_LIMIT 60.0f
#define GRACE_MS 600000UL // 10 min sustained before excursion alarm
#define LOG_MS 60000UL
const char *zoneName[NZONES] =
{"Door","Warm corner","Cold corner","High","Product"};
Adafruit_SHT31 sht[NZONES]; // via mux/addresses in practice
RTC_DS3231 rtc;
WiFiClient net; PubSubClient mqtt(net);
struct MKT { double sumExp=0; uint32_t n=0; } mkt;
const double DH_R = 83000.0/8.314;
struct Exc { bool active=false; uint32_t start=0; float peak=-99; } exc;
uint32_t lastLog=0, seq=0;
void mktAdd(float t){ mkt.sumExp += exp(-DH_R/(t+273.15)); mkt.n++; }
float mktValue(){ return mkt.n? (float)(DH_R/-log(mkt.sumExp/mkt.n)-273.15):NAN; }
float dewPoint(float t,float rh){ const float a=17.27f,b=237.7f;
float g=logf(rh/100.0f)+a*t/(b+t); return b*g/(a-g); }
void logLine(DateTime t, float *zt, float *zh, float tRep){
File f = SD.open("/climate.csv", FILE_APPEND); // append-only
if(!f) return;
f.printf("%lu,%04d-%02d-%02d %02d:%02d:%02d",(unsigned long)seq++,
t.year(),t.month(),t.day(),t.hour(),t.minute(),t.second());
for(int i=0;i<NZONES;i++) f.printf(",%.2f,%.1f", zt[i], zh[i]);
f.printf(",%.2f,%.2f\n", mktValue(), tRep);
f.close();
}
void setup(){
Serial.begin(115200);
pinMode(PIN_BUZZER, OUTPUT);
Wire.begin(21,22);
for(int i=0;i<NZONES;i++) sht[i].begin(0x44); // + mux select
rtc.begin(); SD.begin(SD_CS);
WiFi.begin(WIFI_SSID,WIFI_PASS); mqtt.setServer(MQTT_HOST,1883);
}
void loop(){
if(!mqtt.connected()&&WiFi.status()==WL_CONNECTED) mqtt.connect("climate-1");
mqtt.loop();
uint32_t now = millis();
if (now-lastLog < LOG_MS) return;
lastLog = now;
float zt[NZONES], zh[NZONES], tRep=-99, rhRep=0;
for(int i=0;i<NZONES;i++){
selectMux(i);
zt[i]=sht[i].readTemperature(); zh[i]=sht[i].readHumidity();
if(zt[i]>tRep) tRep=zt[i];
if(zh[i]>rhRep) rhRep=zh[i];
}
DateTime t = rtc.now();
mktAdd(tRep);
logLine(t, zt, zh, tRep); // always, even offline
// proactive near-limit warning + excursion
if (tRep > T_LIMIT-1.0f) mqtt.publish("wh/1/warn","approaching temp limit");
if (rhRep > RH_LIMIT-3.0f) mqtt.publish("wh/1/warn","approaching RH limit");
if (tRep > T_LIMIT){
if(!exc.active){ exc.active=true; exc.start=now; exc.peak=tRep; }
if(tRep>exc.peak) exc.peak=tRep;
if(now-exc.start>GRACE_MS){ digitalWrite(PIN_BUZZER,HIGH);
mqtt.publish("wh/1/alarm","temp excursion"); }
} else if(exc.active){
char m[120]; snprintf(m,sizeof m,"{\"exc_end\":1,\"peak\":%.2f}",exc.peak);
mqtt.publish("wh/1/excursion", m);
exc.active=false; digitalWrite(PIN_BUZZER,LOW);
}
char m[220];
snprintf(m,sizeof m,"{\"tRep\":%.2f,\"rhRep\":%.1f,\"mkt\":%.2f,"
"\"dew\":%.2f}", tRep, rhRep, mktValue(), dewPoint(tRep,rhRep));
mqtt.publish("wh/1/live", m); // mirror off-device
}
Configuration & Calibration
Configuration steps
- Place and map sensors to worst-case zones; set the temperature/humidity limits and grace period.
- Configure the append-only log, off-device mirror, RTC/NTP, and battery-backup behaviour.
- Set MKT/dew-point and near-limit warning thresholds.
- Configure audit export and access controls.
Calibration procedure
An uncalibrated sensor produces confident, precise, wrong numbers. Do this once per physical unit and record the constants.
Sensor accuracy
Calibrate/verify each T/RH sensor against a reference; record offsets. For regulated use, use appropriately qualified/calibrated sensors.
Placement
Confirm sensors are at the genuinely worst/most-variable points; validate with a survey across a day.
Clock/logging
Verify accurate timestamps (RTC backup) and that the append-only log and off-device mirror work and survive a power-off.
Network Architecture & Connectivity
Communication protocol
Readings, MKT/dew and excursions publish on a slow cadence and immediately on excursion/warning; the local append-only log is authoritative and mirrored off-device.
| Topic / endpoint | Direction | Payload |
|---|---|---|
wh/1/live | node → store | per-zone T/RH, worst-case, MKT, dew |
wh/1/excursion | node → store | excursion start/end, duration, peak |
wh/1/warn | node → QA | approaching-limit warning |
Message contract between the device and the broker.
Cloud platform configuration
A compliance store mirrors the record, keeps the audit trail, and supports export/reporting for inspectors and customers; near-limit warnings drive preventive action.
Dashboard setup
Per-zone and worst-case trends, MKT/dew, an excursion/door timeline, and an audit-export button.
Mobile app integration
Near-limit warnings and excursion alerts, and power-loss/battery notifications.
Security considerations
- TLS and append-only, sequence-numbered logs mirrored off-device.
- Restrict audit access; keep the record tamper-evident.
- Alert on power/connectivity loss and on a node going silent.
Testing Procedure & Expected Output
Test from the bottom up. Confirm power, then each sensor in isolation, then the integrated loop — the first failing step tells you exactly where to look.
| Test | What you should see |
|---|---|
| Warm a zone above limit briefly then recover | Excursion logged with duration/peak; near-limit warning first |
| Compare zones | Worst zone drives compliance evaluation |
| Cut mains power | Battery keeps logging locally with correct timestamps |
| Drop the network | Local log continues; mirrors/backfills on reconnect |
| Attempt to edit a past record | Sequence break makes tampering detectable |
| Export the audit trail | Complete, timestamped record suitable for inspection |
Bench-test checklist. If a row fails, stop and fix it before moving on.
Expected output
The dashboard shows each zone's T/RH, the worst-case values, MKT and dew point, and an excursion/door-event timeline; an export produces the audit trail.
seq,timestamp,door_t,door_rh,corner_t,corner_rh,...,mkt,tRep
20431,2026-07-27 14:00:00,24.1,54,23.6,52,...,23.8,24.6
20432,2026-07-27 14:01:00,26.3,58,23.7,52,...,23.9,26.3
# near-limit warning 14:01; excursion if sustained > grace
The door zone spiking to 26.3 °C (worst-case) drives a near-limit warning and, if sustained, an excursion record — the representative, tamper-evident evidence that proves conditions and flags risk.
Troubleshooting: Common Errors & Fixes
Performance Optimisation
- A one-minute cadence suits warehouse climate; publish warnings/excursions immediately.
- Compute MKT incrementally; keep the log append-only and rotate by day/month.
- Ride outages on battery + local log; backfill the mirror on reconnect.
- Use accurate sensors and worst-case placement for a trustworthy record.
- Replace every
delay()with amillis()comparison — blocking delays are the single most common cause of dropped readings. - Sample sensors on a fixed cadence and publish on a slower one; you almost never need to transmit at the sampling rate.
- Move networking into its own FreeRTOS task so a slow DNS lookup cannot stall the control loop.
- Use
uint8_t/uint16_twhere the range allows; on an 8-bit AVR a 32-bit add costs four times as much. - Batch several samples into one MQTT publish. Radio time, not CPU time, dominates the energy budget.
- Set the MQTT keep-alive to a value that matches your reporting interval so the broker does not churn reconnections.
- For battery builds use deep sleep between samples: an ESP32 drops from ~160 mA awake to about 10 µA asleep, which is the difference between days and months of runtime.
Safety Precautions
- For regulated storage (e.g. pharma GDP), use validated/qualified/calibrated instruments — a DIY logger is supplementary unless validated to the applicable standard.
- Placement and calibration decide accuracy; sensors must be at the worst points and verified.
- Keep the record tamper-evident and mirrored so it is defensible.
- Alert before limits to prevent damage, and maintain outage ride-through.
- Lithium cells vent and burn when abused. Only use protected cells or a proper BMS, never charge below 0 °C, and never leave a charging pack unattended on a wooden desk.
- Wear eye protection when soldering or cutting, and solder in a ventilated space — rosin flux fumes are a respiratory irritant.
- Power the circuit through a bench supply with a current limit while you are testing. A 300 mA limit turns a wiring mistake into a beep instead of a dead board.
- Disconnect power before changing any wiring. Hot-plugging a sensor onto a live bus is the fastest way to lose a controller.
Maintenance
- Re-verify/calibrate sensors on a schedule; replace the RTC coin cell.
- Confirm placement still represents the worst conditions as storage/use changes.
- Check the off-device mirror, battery backup and backfill.
- Export/archive the audit trail per retention policy.
- Re-check every screw terminal and header after the first week — thermal cycling loosens connections that felt tight on day one.
- Rotate the microSD card annually and keep an image of the working system. Cards used as loggers wear out silently.
- Log pack voltage. When resting voltage after a full charge drops below about 4.0 V, the cell is near end of life — replace it.
- Keep the broker and dashboard containers patched, and rotate device credentials at least once a year.
- Recalibrate at the interval given in the calibration section, and keep the constants in a text file next to the firmware — not only in flash.
- Keep a short logbook of firmware versions and what changed. Six months later you will not remember why that constant is 1.083.
Future Improvements & Upgrades
A working v1 is a platform, not a finish line. These are the upgrades that add the most capability for the least rework.
- Add CO₂/particulate for specific storage needs.
- Add wireless zone nodes for easy coverage.
- Cryptographically sign records for a stronger audit trail.
- Integrate with a validated compliance/QMS system.
- Design a proper PCB. Once the breadboard version has run for a month, moving to a two-layer board removes the intermittent-contact failures that dominate prototype faults.
- Add over-the-air firmware updates so you never have to physically reach a deployed node again.
- Add persistent local storage (microSD or the on-chip flash) so a network outage does not create a hole in your data.
- Move configuration out of the source: a captive-portal setup page or a JSON config file makes the build reusable without a recompile.
- Add a battery and solar option so the unit survives a power cut and can be sited away from a socket.
- Write a small test harness that feeds synthetic sensor values through the decision logic, so you can validate thresholds without physically triggering the event.
Frequently Asked Questions
References & Learning Resources
These are the primary sources worth reading in full. Manufacturer datasheets always outrank forum posts when the two disagree.