Greenhouse automation keeps temperature and humidity in the range your plants need by switching fans, heaters and misting automatically instead of relying on someone to open vents at the right moment. In this tutorial you will design the MQTT topics, write a Node-RED flow that controls three actuators with hysteresis and interlocks, add a stale-data watchdog, and make the relay nodes safe on their own if the network goes down.
The Architecture
The system has three parts that only communicate through an MQTT broker:
Climate sensor (ESP32 + SHT31/BME280)
| publishes telemetry every 30 s
v
MQTT broker <----> Node-RED (decision logic)
^ |
| publishes /set |
+---------------------+
|
Relay node (ESP32 + 3 relays): fan, heater, mister
publishes /state after acting
Separating sensing, logic and actuation has real benefits. You can replace the sensor without touching the logic, change setpoints from a phone without reflashing anything, and add a dashboard or data logger later as one more subscriber. If you need a refresher on brokers, read about what an MQTT broker does first.
Topic Design for Greenhouse Automation
Use the public test broker while building. Replace g2w6n3 with your own random string, because the public area is shared and readable by anyone.
| Topic | Direction | Payload | Retained |
|---|---|---|---|
connectmq/demo/g2w6n3/greenhouse/climate |
sensor to all | {"temp":27.4,"hum":58.2} |
yes |
connectmq/demo/g2w6n3/greenhouse/setpoints |
phone/dashboard to Node-RED | {"heat_on":12,"vent_on":28,"mist_on":45} |
yes |
connectmq/demo/g2w6n3/greenhouse/fan/set |
Node-RED to relay | ON / OFF |
no |
connectmq/demo/g2w6n3/greenhouse/heater/set |
Node-RED to relay | ON / OFF |
no |
connectmq/demo/g2w6n3/greenhouse/mister/set |
Node-RED to relay | ON / OFF |
no |
connectmq/demo/g2w6n3/greenhouse/<actuator>/state |
relay to all | ON / OFF |
yes |
connectmq/demo/g2w6n3/greenhouse/alarm |
Node-RED to all | {"type":"stale_sensor"} |
no |
Setpoints are retained so that Node-RED receives the current values immediately after a restart. Commands are not retained, so a relay that reboots does not replay an old instruction.
Building the Node-RED Flow
Create the flow with these nodes. If you are new to Node-RED, the Node-RED MQTT tutorial covers the basics of broker configuration.
| Node | Settings |
|---|---|
mqtt-broker config |
Server broker.connectmq.com, port 1883, client ID nodered-gh-g2w6n3, protocol MQTT 3.1.1 |
mqtt in "climate" |
Topic connectmq/demo/g2w6n3/greenhouse/climate, output "a parsed JSON object" |
mqtt in "setpoints" |
Topic connectmq/demo/g2w6n3/greenhouse/setpoints, output "a parsed JSON object" |
function "climate logic" |
Code below, 1 output, wired from both mqtt in nodes |
mqtt out "actuators" |
Topic empty (taken from msg.topic), QoS 1, retain false |
inject "watchdog tick" |
Repeat every 60 seconds |
function "watchdog" |
Code below, wired to the same mqtt out |
The climate logic function
const BASE = "connectmq/demo/g2w6n3/greenhouse";
// Defaults, overridden by retained setpoints message
const sp = flow.get("sp") || { heat_on: 12, heat_off: 14, vent_on: 28, vent_off: 25, mist_on: 45, mist_off: 55, mist_max_temp: 35 };
if (msg.topic.endsWith("/setpoints")) {
const p = msg.payload || {};
if (p.heat_on !== undefined) { sp.heat_on = p.heat_on; sp.heat_off = p.heat_on + 2; }
if (p.vent_on !== undefined) { sp.vent_on = p.vent_on; sp.vent_off = p.vent_on - 3; }
if (p.mist_on !== undefined) { sp.mist_on = p.mist_on; sp.mist_off = p.mist_on + 10; }
flow.set("sp", sp);
node.status({ fill: "blue", shape: "ring", text: "setpoints " + JSON.stringify(p) });
return null;
}
const t = Number(msg.payload.temp);
const h = Number(msg.payload.hum);
if (!Number.isFinite(t) || !Number.isFinite(h) || t < -20 || t > 60 || h < 0 || h > 100) {
node.warn("Implausible reading ignored: " + JSON.stringify(msg.payload));
return null;
}
flow.set("lastSeen", Date.now());
const st = flow.get("state") || { fan: "OFF", heater: "OFF", mister: "OFF" };
const next = Object.assign({}, st);
// Heating with hysteresis
if (t <= sp.heat_on) next.heater = "ON";
else if (t >= sp.heat_off) next.heater = "OFF";
// Ventilation with hysteresis
if (t >= sp.vent_on) next.fan = "ON";
else if (t <= sp.vent_off) next.fan = "OFF";
// Misting only when warm enough to evaporate and not freezing the crop
if (h <= sp.mist_on && t > 18 && t < sp.mist_max_temp) next.mister = "ON";
else if (h >= sp.mist_off || t <= 18) next.mister = "OFF";
// Interlocks: never heat and vent together, never mist while heating
if (next.heater === "ON") { next.fan = "OFF"; next.mister = "OFF"; }
flow.set("state", next);
const out = [];
for (const a of ["fan", "heater", "mister"]) {
if (next[a] !== st[a] || msg.forceSend) {
out.push({ topic: `${BASE}/${a}/set`, payload: next[a] });
}
}
node.status({ fill: "green", shape: "dot", text: `${t}°C ${h}% F:${next.fan} H:${next.heater} M:${next.mister}` });
return [out];
Returning [out] sends every message in the array, one after another, through the single output to the mqtt out node. Commands are only sent when a state changes, which keeps traffic low and the relay logs readable.
The watchdog function
const BASE = "connectmq/demo/g2w6n3/greenhouse";
const last = flow.get("lastSeen") || 0;
const ageMin = (Date.now() - last) / 60000;
if (last && ageMin > 5 && !flow.get("staleAlarm")) {
flow.set("staleAlarm", true);
flow.set("state", { fan: "OFF", heater: "OFF", mister: "OFF" });
return [[
{ topic: `${BASE}/heater/set`, payload: "OFF" },
{ topic: `${BASE}/mister/set`, payload: "OFF" },
{ topic: `${BASE}/alarm`, payload: JSON.stringify({ type: "stale_sensor", minutes: Math.round(ageMin) }) }
]];
}
if (ageMin <= 5) flow.set("staleAlarm", false);
return null;
If the sensor stops reporting, the watchdog switches off the heater and mister, because acting on old data is worse than doing nothing. The fan is left alone; with no data, a relay's own failsafe (below) decides.
Testing the Flow
Simulate a hot, dry afternoon and then a cold night:
mosquitto_pub -h broker.connectmq.com -p 1883 -i sim-g2w6n3 -r \
-t 'connectmq/demo/g2w6n3/greenhouse/climate' -m '{"temp":30.2,"hum":40}'
mosquitto_pub -h broker.connectmq.com -p 1883 -i sim-g2w6n3 -r \
-t 'connectmq/demo/g2w6n3/greenhouse/climate' -m '{"temp":10.5,"hum":80}'
mosquitto_sub -h broker.connectmq.com -p 1883 -i mon-g2w6n3 \
-t 'connectmq/demo/g2w6n3/greenhouse/+/set' -v
The first message should switch the fan and mister on; the second should switch both off and the heater on. Change setpoints live by publishing {"vent_on":26} to the setpoints topic.
Making the Relay Node Safe on Its Own
Node-RED decides, but the relay node must never trust the network blindly. Build these rules into its firmware:
- Maximum run time per actuator. The mister turns itself off after, say, five minutes even if no
OFFarrives. A stuck valve can waterlog a crop. - Local over-temperature cut-off. If the relay node has its own temperature probe, it switches the heater off above a hard limit regardless of commands.
- Safe state on disconnect. When MQTT drops for more than a minute, heater and mister go off. Decide whether the fan should go on or off for your climate; in hot regions, venting is usually the safer default.
- Publish state after acting. The
/statetopics show what actually happened, which lets Node-RED and dashboards detect a relay that ignores commands. - Last will. Connect with a retained
offlinewill on a status topic so a missing relay is visible immediately.
For relay wiring and an ESP32 sketch that subscribes to command topics, see the ESP32 relay MQTT tutorial. Mains-voltage heaters and fans must be switched through properly rated contactors installed by someone qualified.
Tuning Tips
- Hysteresis gaps of 2 to 3 °C prevent short cycling, which wears out relays and motors.
- Sensor placement matters more than sensor accuracy: shade the sensor from direct sun, mount it at plant height and give it airflow.
- Night and day setpoints are easy to add: an
injectnode at sunrise and sunset can publish new retained setpoints. - Log everything. A Node-RED flow or Telegraf instance subscribed to
greenhouse/#gives you the history to tune setpoints over a season.
For the bigger picture of sensors, connectivity and topic design across a whole farm, read the smart agriculture IoT guide, and pair this greenhouse with the automatic irrigation system for watering.
Watching the Greenhouse Remotely
Automation is only trustworthy if you can see what it is doing. Three views cover most needs. A Node-RED Dashboard page with gauges for temperature and humidity, switches showing the actuator /state topics and sliders that publish new setpoints gives growers a simple control panel on a phone. A historian, such as Telegraf writing to InfluxDB, lets you compare this week's nights with last week's and spot a heater that runs longer than it used to. And a live topic view on the broker side shows every command and state message as it happens.
When the broker is ConnectMQ, the portal's live topic explorer and dashboard provide that last view without installing anything, and the in-browser MQTT client is handy for publishing a test setpoint from a laptop in the greenhouse. Whatever tool you use, compare /set with /state regularly: a command that is never confirmed points to a failing relay, a loose wire or a node that has lost Wi-Fi.
Going to Production
Once the logic works, move off the public test area. Create a private broker, switch the mqtt-broker node to port 8883 with TLS enabled, and enter your issued username, password and client ID. Every device gets its own credentials. A fully managed MQTT service keeps the broker running when the greenhouse PC does not, and lets you check the climate from anywhere. The IoT automation guide shows how this pattern extends to homes and buildings.
Run Your Greenhouse on ConnectMQ
ConnectMQ gives your greenhouse a private broker with TLS, a live topic explorer to watch commands and states, and threshold alert rules as a backup alarm for frost or overheating. Create a free Serverless broker at app.connectmq.com, no credit card required, and point Node-RED and your ESP32 nodes at it.
Frequently Asked Questions
Can greenhouse automation run without the internet?
Yes, if the broker and Node-RED are on the local network. With a cloud broker you gain remote access, so put the critical failsafes in the relay firmware so the greenhouse stays safe during an outage.
Which temperature sensor is best for a greenhouse?
An SHT31 or SHT4x handles high humidity better than a DHT22. Whatever you choose, shield it from direct sun with a ventilated radiation shield.
Why does my fan switch on and off constantly?
The gap between on and off thresholds is too small, or the sensor sits in the fan's airflow. Widen the hysteresis and move the sensor.
Should Node-RED or the ESP32 make control decisions?
Use Node-RED for flexible logic and setpoints, and keep simple safety limits on the ESP32 so actuators fail safe if the connection is lost.



