Smart Manufacturing

Cold Storage Temperature Monitoring with MQTT Sensors and Excursion Alerts

Monitor freezers and cold rooms with ESP32 sensors publishing over MQTT, detect real temperature excursions without nuisance alarms from door openings, flag offline sensors and keep an excursion log.

Cold storage temperature monitoring with wireless sensors in a walk-in freezer sending excursion alerts to a cloud MQTT dashboard
Run this on your own MQTT brokerGet a private, managed MQTT broker on the Free plan and start from ready-made code samples.
Try it free Code samples No card required

Cold storage temperature monitoring protects food, ingredients, pharmaceuticals and samples that can be lost in a few hours when a freezer or cold room fails. This use case shows how to put MQTT sensors into freezers and cold rooms, distinguish real temperature excursions from harmless door openings, detect sensors that go offline and keep an excursion log you can show an auditor.

What Can Go Wrong in Cold Storage

Most cold storage losses do not come from dramatic breakdowns. They come from ordinary events that nobody notices in time:

  • A door left ajar after a night delivery.
  • A compressor that trips on a hot afternoon and does not restart.
  • An evaporator that ices up, so the room warms slowly over hours.
  • A power cut over the weekend.
  • A defrost cycle that runs too long after a controller change.

A manual temperature log, filled in twice per shift, catches none of these between readings. Continuous monitoring with alerts closes that gap. The same principles apply to refrigerated transport, covered in the cold chain monitoring article; this one focuses on fixed freezers and cold rooms in a plant, warehouse or kitchen.

The Setup

Component Role Example choice
Temperature probe Measures air or product temperature Waterproof DS18B20 on a flat cable through the door seal
Door contact Explains short temperature rises Reed switch on the door frame
Sensor node Reads probe and door, publishes ESP32 mounted outside the freezer on mains power
MQTT broker Receives readings and alerts ConnectMQ managed broker
Alert logic Excursion and offline detection Portal threshold rules plus a Python service
Excursion log Evidence for audits and claims Database written by the alert service

Keep the ESP32 outside the cold space. Electronics and batteries struggle at freezer temperatures, and Wi-Fi signals do not pass through the metal walls of a cold room. Only the probe cable goes inside.

Where to put the probe

Air temperature near the evaporator changes fast and exaggerates every door opening. Product temperature changes slowly and reflects what actually matters. A common compromise is to place the probe in the warmest part of the room, away from the evaporator outlet, and optionally inside a small bottle of glycol or a product simulant that buffers it, so readings follow product temperature rather than air gusts. Map the room once with a few probes to find the warm spot before choosing the permanent position.

Sensor Firmware

The sketch below publishes temperature and door state every 30 seconds, and immediately when the door changes. It sets a Last Will message, so the broker announces the sensor as offline if it disappears. Install PubSubClient, OneWire and DallasTemperature. For sensor wiring details, see the ESP32 DS18B20 MQTT tutorial.

#include <WiFi.h>
#include <PubSubClient.h>
#include <OneWire.h>
#include <DallasTemperature.h>

const char* WIFI_SSID = "YOUR_WIFI";
const char* WIFI_PASS = "YOUR_WIFI_PASSWORD";
const char* CLIENT_ID = "freezer2-6c0f3b";
const char* BASE = "connectmq/demo/6c0f3b/warehouse/freezer2";

const int PROBE_PIN = 4;
const int DOOR_PIN = 27;      // reed switch to GND, closed door = LOW
const uint32_t PERIOD_MS = 30000;

OneWire oneWire(PROBE_PIN);
DallasTemperature probe(&oneWire);
WiFiClient net;
PubSubClient mqtt(net);
char topicTelemetry[96], topicStatus[96];
int lastDoor = -1;
uint32_t lastPub = 0;

void connectAll() {
  if (WiFi.status() != WL_CONNECTED) {
    WiFi.begin(WIFI_SSID, WIFI_PASS);
    while (WiFi.status() != WL_CONNECTED) delay(250);
  }
  while (!mqtt.connected()) {
    if (mqtt.connect(CLIENT_ID, topicStatus, 1, true, "offline")) {
      mqtt.publish(topicStatus, "online", true);
    } else {
      delay(3000);
    }
  }
}

void publishReading(bool doorOpen) {
  probe.requestTemperatures();
  float t = probe.getTempCByIndex(0);
  char payload[128];
  if (t == DEVICE_DISCONNECTED_C) {
    snprintf(payload, sizeof(payload), "{\"temp_c\":null,\"door_open\":%s,\"fault\":\"probe\"}",
             doorOpen ? "true" : "false");
  } else {
    snprintf(payload, sizeof(payload), "{\"temp_c\":%.2f,\"door_open\":%s,\"rssi\":%d}",
             t, doorOpen ? "true" : "false", WiFi.RSSI());
  }
  mqtt.publish(topicTelemetry, payload, true);
}

void setup() {
  pinMode(DOOR_PIN, INPUT_PULLUP);
  snprintf(topicTelemetry, sizeof(topicTelemetry), "%s/telemetry", BASE);
  snprintf(topicStatus, sizeof(topicStatus), "%s/status", BASE);
  probe.begin();
  WiFi.mode(WIFI_STA);
  mqtt.setServer("broker.connectmq.com", 1883);
  connectAll();
}

void loop() {
  connectAll();
  mqtt.loop();
  int door = digitalRead(DOOR_PIN) == HIGH;  // HIGH = magnet away = open
  if (door != lastDoor || millis() - lastPub >= PERIOD_MS) {
    publishReading(door);
    lastDoor = door;
    lastPub = millis();
  }
  delay(200);
}

The Last Will is the safety net that a manual log never had: if the node loses power or Wi-Fi, the broker publishes offline on its status topic after the keepalive timeout. The ESP32 MQTT Last Will guide explains the mechanism.

Excursion Logic Without Nuisance Alarms

The naive approach, alarm whenever temperature exceeds the limit, fails within a day. Every delivery opens the door, air temperature spikes, and staff learn to ignore the alarm. Good cold storage temperature monitoring uses a few rules together:

  1. Limit plus delay. Raise an excursion only if the temperature stays above the limit for a set time, for example 15 minutes for a freezer.
  2. Door-aware delay. While the door is open, allow a longer grace period, but raise a separate door-open alert if it stays open too long.
  3. Hard limit. A much higher temperature triggers immediately, regardless of delay.
  4. Offline and probe faults. A silent sensor or a disconnected probe is an alert in its own right.
  5. Excursion record. Each excursion gets a start time, end time, peak temperature and duration.

The limits themselves come from your products and your food safety or quality plan. Use those documents rather than generic numbers.

Excursion service in Python

import json
import sqlite3
import time
import paho.mqtt.client as mqtt

PREFIX = "connectmq/demo/6c0f3b/warehouse"
LIMIT_C = -15.0          # from your product requirements
HARD_LIMIT_C = -5.0
DELAY_S = 15 * 60
DOOR_MAX_S = 5 * 60
STALE_S = 3 * 60

db = sqlite3.connect("excursions.db", check_same_thread=False)
db.execute("""CREATE TABLE IF NOT EXISTS excursions (
    unit TEXT, start_ts INTEGER, end_ts INTEGER, peak_c REAL, reason TEXT)""")
units = {}

def alert(client, unit, kind, detail):
    msg = {"ts": int(time.time()), "unit": unit, "kind": kind, **detail}
    client.publish(f"{PREFIX}/{unit}/alert", json.dumps(msg), qos=1, retain=True)
    print("ALERT", msg)

def on_connect(client, userdata, flags, reason_code, properties):
    client.subscribe(f"{PREFIX}/+/telemetry", qos=1)
    client.subscribe(f"{PREFIX}/+/status", qos=1)

def on_message(client, userdata, msg):
    unit = msg.topic.split("/")[4]
    now = time.time()
    u = units.setdefault(unit, {"above_since": None, "door_since": None,
                                "excursion": None, "seen": now})
    if msg.topic.endswith("/status"):
        if msg.payload == b"offline":
            alert(client, unit, "offline", {})
        return

    d = json.loads(msg.payload)
    u["seen"] = now
    if d.get("temp_c") is None:
        alert(client, unit, "probe_fault", {})
        return
    t = d["temp_c"]

    u["door_since"] = (u["door_since"] or now) if d["door_open"] else None
    if u["door_since"] and now - u["door_since"] > DOOR_MAX_S:
        alert(client, unit, "door_open_too_long", {"seconds": int(now - u["door_since"])})
        u["door_since"] = now

    if t > LIMIT_C:
        u["above_since"] = u["above_since"] or now
        delay = DELAY_S * (2 if d["door_open"] else 1)
        exc = u["excursion"]
        if exc:
            exc["peak"] = max(exc["peak"], t)
        elif t > HARD_LIMIT_C or now - u["above_since"] > delay:
            u["excursion"] = {"start": u["above_since"], "peak": t}
            alert(client, unit, "excursion_start", {"temp_c": t, "limit_c": LIMIT_C})
    else:
        exc = u["excursion"]
        if exc:
            db.execute("INSERT INTO excursions VALUES (?,?,?,?,?)",
                       (unit, int(exc["start"]), int(now), exc["peak"], "above_limit"))
            db.commit()
            alert(client, unit, "excursion_end",
                  {"duration_min": round((now - exc["start"]) / 60, 1), "peak_c": exc["peak"]})
        u["above_since"] = None
        u["excursion"] = None

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="cold-alerts-6c0f3b")
client.on_connect = on_connect
client.on_message = on_message
client.connect("broker.connectmq.com", 1883, keepalive=60)
client.loop_start()

while True:
    time.sleep(30)
    for unit, u in units.items():
        if time.time() - u["seen"] > STALE_S:
            alert(client, unit, "stale", {"seconds_silent": int(time.time() - u["seen"])})
            u["seen"] = time.time()

The excursion start time is recorded as the moment the temperature first crossed the limit, not when the delay expired, so the log shows the true duration. For the simplest cases you can also create threshold alert rules on the temp_c field directly in the ConnectMQ portal and review every crossing in its alerts log, keeping the Python service for the delay and door logic.

Getting Alerts to People

The service publishes alerts to MQTT; something still has to reach a phone at 3 a.m. Because ConnectMQ does not send emails or SMS itself, the honest pattern is an external tool that subscribes to the alert topics: a Node-RED flow with an email or messaging node, Home Assistant notifications for smaller sites, or a few extra lines in the Python service that call your notification provider. Define an escalation path: first the shift lead, then the maintenance on-call, then the site manager if nobody acknowledges.

Handling Defrost Cycles

Automatic defrost is the other frequent source of false alarms. During defrost the evaporator heats up and air temperature near it rises for a short time, which is normal. Three measures keep it from triggering excursions. First, keep the probe away from the evaporator, as described above. Second, make sure the excursion delay is longer than a normal defrost period on that unit, which you can see clearly in a day of recorded data. Third, if the refrigeration controller exposes a defrost signal, publish it alongside the temperature and extend the delay while defrost is active, exactly as the service does for door openings. A defrost that runs much longer than usual is itself worth an alert, because it often points to a failing heater, sensor or controller setting.

Rollout Checklist

  1. Map each room with several probes for a day to find the warm spot.
  2. Install one node per unit, electronics outside, probe at the warm spot.
  3. Set limits and delays from your product requirements and quality plan.
  4. Test the alarms: open the door, unplug a node, disconnect a probe, and confirm each alert arrives.
  5. Calibrate probes against a reference thermometer and record the check.
  6. Review the excursion log weekly and act on repeat offenders, such as a door that is often left open.
  7. Move to a private broker with credentials from the portal and TLS on port 8883 before going live.

From Demo to Production

The tutorial runs on the public MQTT test broker, where all topics are visible to anyone, which is fine for testing but not for operational data. For production, a cloud MQTT broker gives every freezer node an encrypted, authenticated connection and lets the alert service, dashboards and the quality team subscribe from anywhere. Temperature records also complement IoT quality control in manufacturing and batch manufacturing traceability, since excursion windows can be linked to the lots stored at the time. The smart manufacturing guide puts all of this into a plant-wide architecture.

Monitor Your Cold Storage on ConnectMQ

ConnectMQ provides a private MQTT broker with TLS, Last Will support, retained messages, a live dashboard and threshold alert rules in the portal. Create a broker on the free Serverless plan at app.connectmq.com, no credit card required, and connect your first freezer today.

Frequently Asked Questions

How often should cold storage temperature be recorded?

Every 30 seconds to 5 minutes is typical for continuous monitoring. Faster reporting gives quicker alerts, while the excursion delay prevents nuisance alarms.

How do I avoid alarms every time the door opens?

Require the temperature to stay above the limit for a delay period before alerting, extend that delay while the door is open, and alert separately if the door stays open too long.

What happens if a sensor loses power?

With a Last Will message, the broker publishes an offline status after the keepalive timeout, and the alert service treats it as an alarm.

Should the probe measure air or product temperature?

Product temperature matters most. Placing the probe in a buffer such as a glycol bottle at the warmest spot gives readings that follow product temperature and ignore brief air spikes.

Related articles

OEE vs TEEP vs OOE: Which Metric Should You Use?

OEE, OOE and TEEP share the same performance and quality factors but measure against different time bases. This comparison works through one example and lists the MQTT signals each metric needs.

7 min read

All articles →

Your private MQTT broker is a few clicks away

Create an account, get your broker credentials and connect your first device — no servers to install, patch or scale.