Smart Fire Alarm: Dual ESP-01S Sensors with MQTT, Node-RED & Remote Monitoring via Ngrok

01 Overview
This project presents a Wi-Fi-enabled fire alarm system built using two ESP-01S (ESP8266) modules communicating over the MQTT protocol through the HiveMQ public cloud broker. The system is designed to detect early signs of fire by monitoring both temperature and combustible gas levels in real time.
Node 1 is equipped with a DS18B20 digital temperature sensor and a buzzer. It continuously monitors the ambient temperature and automatically triggers the buzzer when the reading exceeds 35°C, serving as a heat-based fire alert.
Node 2 is equipped with an MQ-9 gas sensor and an LED. It detects the presence of combustible gases or carbon monoxide in the environment and activates the LED when gas is detected.
Both nodes transmit sensor data wirelessly to a Node-RED dashboard, which serves as the central monitoring interface. The dashboard displays live temperature readings, gas detection status, and node connectivity. It also allows the user to remotely control Node 1's buzzer and Node 2's LED.
Project Use Case
This fire alarm system is suited for homes, small offices, server rooms, laboratories, and any enclosed space where early detection of heat or combustible gas is critical. Key use cases include:
- Home Fire Safety — Monitors kitchen or utility areas for abnormal temperature rises or gas leaks from LPG stoves and detects early warning signs before a fire develops.
- Office and Workplace Safety — Provides continuous background monitoring of temperature and air quality, alerting staff remotely through the Node-RED dashboard without requiring physical presence near the sensors.
- Server Room Monitoring — Detects overheating conditions caused by equipment failure and sends immediate alerts to operators before hardware damage or fire occurs.
- IoT and MQTT Learning Platform — Serves as a hands-on project for students and hobbyists learning Wi-Fi-based IoT communication, MQTT publish-subscribe architecture, and Node-RED dashboard development.
- Remote Monitoring via Smartphone — The Node-RED dashboard is accessible from any browser on any device, allowing users to monitor sensor readings and control alarm outputs remotely from anywhere using Ngrok.
02 Hardware and Software Components
Gather everything below before you start. This is your checklist — names, models, and versions only.
Hardware Components
| Component | Description |
|---|---|
| ESP-01S (ESP8266) Wi-Fi Module x2 | Node 1 (Temperature) and Node 2 (Gas Detection) |
| DS18B20 Digital Temperature Sensor x1 | 1-Wire temperature sensor for Node 1 |
| MQ-9 Gas / CO Sensor x1 | Detects LPG, CO, and CH4 for Node 2 |
| Active Buzzer x1 | Audible alarm output for Node 1 only — heat-based fire alert |
| LED x1 | Visual alert indicator for Node 2 |
| 220Ω Resistor x1 | Current-limiting resistor for LED |
| 4.7kΩ Resistor x1 | Pull-up resistor for DS18B20 data line (required) |
| MB102 Breadboard Power Supply Module (3.3V / 5V) x1 | Stable power supply for all components. Accepts 6.5V–12V via DC jack. |
| 12V DC Power Adapter x1 | Powers the MB102 via DC jack (recommended: 12V, 1A or higher) |
| Breadboard x1 | Prototyping and connections |
| Pushbutton x2 | Manual reset button for each ESP-01S node — press to pull RST to GND and restart the module |
| Jumper Wires (M-M, M-F) | For making all connections |
| FT232R USB to Serial Module x1 | For flashing firmware to ESP-01S modules |
Software Tools
| Software | Version / Details |
|---|---|
| Arduino IDE | Latest version — writing and uploading firmware to both ESP-01S nodes |
| ESP8266 Board Package | By ESP8266 Community — adds ESP8266 support to Arduino IDE |
| HiveMQ Public MQTT Broker | Free cloud broker at broker.hivemq.com on port 1883 |
| Node-RED | Dashboard creation and flow-based programming |
| Ngrok | Exposes local Node-RED dashboard to the internet via public URL |
| PubSubClient Library | By Nick O'Leary — MQTT communication for both nodes |
| OneWire Library | By Paul Stoffregen — 1-Wire communication for DS18B20 |
| DallasTemperature Library | By Miles Burton — simplified DS18B20 temperature reading |
| Canva | Hardware schematic and wiring diagram |
03 Application Discussion
Here is what each component does and why it is part of this project.
ESP-01S (ESP8266) Wi-Fi Module
The ESP-01S serves as the main controller for each node. It is built around the ESP8266 SoC which integrates a 32-bit microprocessor and a full TCP/IP networking stack, allowing it to connect to Wi-Fi and communicate with the HiveMQ broker independently using the MQTT protocol.
DS18B20 Digital Temperature Sensor
The DS18B20 is a digital temperature sensor that uses the 1-Wire protocol to transmit readings through a single data line. It measures temperatures from -55°C to +125°C with up to 12-bit resolution. In this project, it monitors ambient temperature in Node 1 and automatically triggers the buzzer when the reading exceeds 35°C.
MQ-9 Gas Sensor Module
The MQ-9 is a metal oxide semiconductor gas sensor that detects combustible gases such as CO, CH4, and LPG by measuring changes in resistance of a heated tin dioxide element. It requires 5V and a 20 to 30 second warm-up after power-on. In this project, Node 2 uses its digital threshold output (DO) to detect gas presence and trigger the LED alert.
Active Buzzer
The active buzzer provides audible alerts for Node 1 only. It contains a built-in oscillator circuit that produces sound simply by applying DC voltage, requiring no PWM signal from the microcontroller. It operates on active LOW logic on GPIO0 and activates automatically when temperature exceeds the alert threshold, or remotely via the Node-RED dashboard.
LED Indicator
The LED acts as a visual alert indicator for Node 2. It is connected to GPIO0 through a 220Ω current-limiting resistor and operates on active LOW logic. It blinks every 500ms during a gas alert using non-blocking millis() timing to keep the MQTT connection alive during an active alarm.
MQTT Protocol and HiveMQ Broker
MQTT is a lightweight publish-subscribe messaging protocol designed for IoT devices. All messages pass through a central broker which routes them by topic instead of direct device-to-device communication. In this project, both nodes and the Node-RED dashboard connect to the HiveMQ free public broker at broker.hivemq.com on port 1883 for real-time data exchange.
Node-RED
Node-RED is a flow-based, low-code programming tool built on Node.js. It serves as the central monitoring dashboard of this project, displaying live temperature and gas readings from both nodes, showing node connection status, and allowing the user to remotely control Node 1's buzzer and Node 2's LED through MQTT.
MB102 Breadboard Power Supply Module
The MB102 is a breadboard-compatible voltage regulator module that accepts 6.5V to 12V via DC jack and provides stable 3.3V and 5V output rails. It supplies the 3.3V required by the ESP-01S modules and DS18B20 sensor, and the 5V required by the MQ-9 gas sensor heater for proper operation.
FT232R USB to UART Module
The FT232R is a USB to UART bridge IC by FTDI that allows the Arduino IDE to upload firmware to the ESP-01S over serial communication. It presents itself as a virtual COM port to the computer and is only connected during the flashing stage, then disconnected once the upload is complete.
Pushbutton
The pushbutton acts as a manual reset trigger for each ESP-01S node. When pressed, it pulls the RST pin to GND, forcing the module to restart. This is useful during development and testing when a manual reboot is needed without disconnecting the power supply.
Ngrok
Ngrok is a reverse tunneling tool that exposes the locally running Node-RED dashboard to the internet by assigning a public HTTPS URL that forwards traffic to the user's machine. It allows the dashboard to be accessed remotely from any device on any network without requiring port forwarding or a static IP address.
04 Hardware Setup
Wire the components to the board using the tables below.
Node 1 — ESP-01S to DS18B20 Temperature Sensor + Buzzer
| Component Pin | Board Pin | Description |
|---|---|---|
| DS18B20 VCC | 3.3V | Power supply |
| DS18B20 GND | GND | Ground connection |
| DS18B20 DQ (Data) | GPIO2 | 1-Wire data line with 4.7kΩ pull-up to 3.3V |
| Buzzer (+) | GPIO0 | Buzzer signal — active LOW |
| Buzzer (−) | GND | Ground connection |
| Pushbutton (one leg) | RST | Manual reset — pulls RST to GND when pressed |
| Pushbutton (other leg) | GND | Ground connection |
| CH_PD (EN) | 3.3V | Must be tied HIGH to 3.3V for the module to boot — without this, the ESP-01S will not power on at all |
Assembly Instructions
- Place the ESP-01S Node 1 module on the breadboard.
- Connect the DS18B20 sensor VCC to the 3.3V rail, GND to the GND rail, and DQ to GPIO2.
- Place a 4.7kΩ resistor between the DS18B20 DQ pin and the 3.3V rail.
- Connect the active buzzer positive (+) to GPIO0 and negative (−) to GND.
- Connect one leg of the pushbutton to RST and the other leg to GND.
- Connect the ESP-01S VCC to the 3.3V rail and GND to the GND rail.
- Connect CH_PD (sometimes labeled EN) directly to the 3.3V rail — this pin must be HIGH for the ESP-01S to power on.
Node 2 — ESP-01S to MQ-9 Gas Sensor + LED
| Component Pin | Board Pin | Description |
|---|---|---|
| MQ-9 VCC (Pin 1) | 5V | Sensor heater power supply |
| MQ-9 GND (Pin 2) | GND | Ground connection |
| MQ-9 DO (Pin 3) | GPIO2 | Digital threshold output — LOW when gas detected |
| LED Anode (+) | GPIO0 via 220Ω resistor | Visual alert indicator — active LOW |
| LED Cathode (−) | GND | Ground connection |
| Pushbutton (one leg) | RST | Manual reset — pulls RST to GND when pressed |
| Pushbutton (other leg) | GND | Ground connection |
| CH_PD (EN) | 3.3V | Must be tied HIGH to 3.3V for the module to boot — without this, the ESP-01S will not power on at all |
Assembly Instructions
- Place the ESP-01S Node 2 module on the breadboard.
- Connect the MQ-9 sensor VCC (Pin 1) to the 5V rail, GND (Pin 2) to the GND rail, and DO (Pin 3) to GPIO2.
- Connect the LED anode (+) to GPIO0 through a 220Ω current-limiting resistor and the cathode (−) to GND.
- Connect one leg of the pushbutton to RST and the other leg to GND.
- Connect the ESP-01S VCC to the 3.3V rail and GND to the GND rail.
- Connect CH_PD (sometimes labeled EN) directly to the 3.3V rail — this pin must be HIGH for the ESP-01S to power on.
Shared Components — MB102 Power Supply and FT232R Programmer
| Pin / Signal | Connects To |
|---|---|
| MB102 3.3V rail | VCC of both ESP-01S nodes and DS18B20 |
| MB102 5V rail | VCC of MQ-9 sensor |
| 12V DC Adapter | MB102 DC jack input |
| FT232R TX | RX of ESP-01S |
| FT232R RX | TX of ESP-01S |
| FT232R GND | GND |
Assembly Instructions
- Place the MB102 power supply module at one end of the breadboard.
- Set the first jumper to 3.3V for the ESP-01S modules and DS18B20 sensor.
- Set the second jumper to 5V for the MQ-9 gas sensor heater.
- Connect the 12V DC adapter to the MB102 DC jack.
- Connect the FT232R to the ESP-01S only during firmware flashing — disconnect after upload is complete.
05 Software Setup
Follow these steps in order. Do not skip any steps — they are required for both ESP-01S nodes to work correctly.
Step 1 — Install Arduino IDE
- Open a web browser and go to the official Arduino website at arduino.cc/en/software.
- Download the latest version of the Arduino IDE compatible with your operating system.
- Run the installer after the download is complete.
- Follow the installation instructions and wait for the setup to finish.
- Open the Arduino IDE after installation.
Step 2 — Install ESP8266 Board Package
- Open the Arduino IDE.
- Go to File > Preferences.
- In the Additional Boards Manager URLs field, add the following link:
http://arduino.esp8266.com/stable/package_esp8266com_index.json
- Click OK to save the settings.
- Go to Tools > Board > Boards Manager.
- Search for ESP8266.
- Install the ESP8266 board package by ESP8266 Community.
- After installation, use exactly these settings in Arduino IDE under Tools:
| Setting | Value |
|---|---|
| Board | Generic ESP8266 Module |
| Upload Speed | 115200 |
| CPU Frequency | 80MHz |
| Flash Size | 1MB (FS: 64KB OTA: ~470KB) |
| Flash Mode | DOUT |
| Reset Method | nodemcu |
| Port | Select the correct COM port |
Step 3 — Install Required Libraries
- In the Arduino IDE, go to Sketch > Include Library > Manage Libraries.
- Search and install the following libraries one by one:
| Library | Author |
|---|---|
| PubSubClient | Nick O'Leary |
| OneWire | Paul Stoffregen |
| DallasTemperature | Miles Burton |
Step 4 — Configure Your Wi-Fi Credentials
- Open the Node 1 code in the Arduino IDE.
- Find these two lines near the top of the code:
const char* WIFI_SSID = "YOUR_WIFI_SSID";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
- Replace YOUR_WIFI_SSID with your Wi-Fi network name.
- Replace YOUR_WIFI_PASSWORD with your Wi-Fi password.
- Repeat the same for the Node 2 code.
Step 5 — Flash Node 1 (DS18B20 + Buzzer)
- Connect the FT232R USB to UART module to the Node 1 ESP-01S.
- Pull GPIO0 to GND before powering on to enter flash mode.
- Connect the FT232R to your PC via USB.
- Open the Node 1 code in the Arduino IDE.
- Go to Tools > Board and select Generic ESP8266 Module.
- Go to Tools > Port and select the correct COM port.
- Click the Verify button to compile the code and check for errors.
- After successful verification, click the Upload button.
- Wait for the upload to complete.
- Disconnect GPIO0 from GND and reset the module.
- Open the Serial Monitor at 115200 baud to verify Wi-Fi and MQTT connection.
Step 6 — Flash Node 2 (MQ-9 + LED)
- Disconnect the FT232R from Node 1 and connect it to the Node 2 ESP-01S.
- Pull GPIO0 to GND before powering on to enter flash mode.
- Open the Node 2 code in the Arduino IDE.
- Go to Tools > Board and select Generic ESP8266 Module.
- Go to Tools > Port and select the correct COM port.
- Click the Verify button to compile the code and check for errors.
- After successful verification, click the Upload button.
- Wait for the upload to complete.
- Disconnect GPIO0 from GND and reset the module.
- Open the Serial Monitor at 115200 baud to verify Wi-Fi and MQTT connection.
- Wait 20 to 30 seconds for the MQ-9 sensor to warm up before testing gas detection.
Step 7 — Set Up Node-RED Dashboard
- Install Node-RED on your Windows PC by following the instructions at nodered.org.
- Open a terminal and run node-red to start the server.
- Open a browser and go to http://localhost:1880.
- Go to Manage Palette and install the node-red-dashboard package.
- Add an MQTT In node and configure it to connect to broker.hivemq.com on port 1883. This same broker connection can be reused for all MQTT In and MQTT Out nodes in this flow — you only need to configure it once.
- Subscribe to home/node1/temperature. In the MQTT In node settings, set Output to "a parsed JSON object" so the payload becomes a usable object instead of raw text.
- Add a Change node after it that sets msg.payload to msg.payload.tempC, then connect it to a Gauge widget.
- Subscribe to home/node2/gas, set its MQTT In Output to "a parsed JSON object" as well.
- Add a Change node after it that sets msg.payload to msg.payload.gas, then connect it to a Text widget.
- Subscribe to home/node1/status and home/node2/status and connect them directly to status indicator widgets — these are already plain text strings, so no parsing is needed.
- Add an MQTT Out node with topic home/node1/buzzer, connected to a Switch or Button widget for Node 1's buzzer.
- Add an MQTT Out node with topic home/node2/led, connected to a separate widget for Node 2's LED.
- For each of these two button/switch widgets, open its edit panel and set the Payload (On) field to the exact string ON and the Payload (Off) field to the exact string OFF, with the payload type set to string.
- Click the red Deploy button to activate the flow.
- Open the dashboard at http://localhost:1880/ui and confirm both nodes appear as online.
06 Code
Copy each file below into the Arduino IDE. Make sure to update the Wi-Fi credentials before uploading as described in Step 4 of the Software Setup section.
Node 1 Code — DS18B20 Temperature Sensor + Buzzer
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <OneWire.h>
#include <DallasTemperature.h>
// ── WiFi Credentials ─────────────────────────────────────────────────────────
const char* WIFI_SSID = "YOUR_WIFI_SSID";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
// ── MQTT Broker ───────────────────────────────────────────────────────────────
const char* MQTT_BROKER = "broker.hivemq.com";
const int MQTT_PORT = 1883;
const char* MQTT_CLIENT = "esp01_node1_ds18b20"; // Must be unique per device
// ── MQTT Topics ───────────────────────────────────────────────────────────────
const char* TOPIC_TEMP = "home/node1/temperature";
const char* TOPIC_STATUS = "home/node1/status";
const char* TOPIC_BUZZER = "home/node1/buzzer"; // Subscribe for remote control
// ── Pin Definitions ───────────────────────────────────────────────────────────
#define ONE_WIRE_BUS 2 // GPIO2 — DS18B20 data
#define BUZZER_PIN 0 // GPIO0 — Active/Passive buzzer
// ── Threshold ────────────────────────────────────────────────────────────────
#define TEMP_ALERT_C 35.0 // Buzzer triggers above this temperature (°C)
// ── Timing ───────────────────────────────────────────────────────────────────
#define READ_INTERVAL_MS 5000 // Publish every 5 seconds
#define SERIAL_BAUD 115200
// ── Objects ───────────────────────────────────────────────────────────────────
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
WiFiClient wifiClient;
PubSubClient mqtt(wifiClient);
unsigned long lastReadTime = 0;
bool buzzerState = false;
void connectWiFi();
void connectMQTT();
void readAndPublish();
void mqttCallback(char* topic, byte* payload, unsigned int length);
void setup() {
Serial.begin(SERIAL_BAUD);
delay(200);
pinMode(BUZZER_PIN, OUTPUT);
digitalWrite(BUZZER_PIN, HIGH);
Serial.println("\n=========================================");
Serial.println(" NODE 1 — DS18B20 + MQTT");
Serial.println("=========================================");
sensors.begin();
sensors.setResolution(12);
Serial.print(" DS18B20 sensors found: ");
Serial.println(sensors.getDeviceCount());
connectWiFi();
mqtt.setServer(MQTT_BROKER, MQTT_PORT);
mqtt.setCallback(mqttCallback);
connectMQTT();
}
void loop() {
if (!mqtt.connected()) connectMQTT();
mqtt.loop();
unsigned long now = millis();
if (now - lastReadTime >= READ_INTERVAL_MS) {
lastReadTime = now;
readAndPublish();
}
}
void readAndPublish() {
sensors.requestTemperatures();
float tempC = sensors.getTempCByIndex(0);
float tempF = sensors.getTempFByIndex(0);
if (tempC == DEVICE_DISCONNECTED_C) {
Serial.println("⚠️ DS18B20 disconnected!");
mqtt.publish(TOPIC_STATUS, "sensor_error");
return;
}
char payload[64];
snprintf(payload, sizeof(payload),
"{\"tempC\":%.2f,\"tempF\":%.2f}", tempC, tempF);
mqtt.publish(TOPIC_TEMP, payload, true);
Serial.print("[PUB] ");
Serial.print(TOPIC_TEMP);
Serial.print(" → ");
Serial.println(payload);
if (tempC > TEMP_ALERT_C && !buzzerState) {
buzzerState = true;
digitalWrite(BUZZER_PIN, LOW);
} else if (tempC <= TEMP_ALERT_C && buzzerState) {
buzzerState = false;
digitalWrite(BUZZER_PIN, HIGH);
}
}
void mqttCallback(char* topic, byte* payload, unsigned int length) {
String msg;
for (unsigned int i = 0; i < length; i++) msg += (char)payload[i];
msg.trim();
Serial.print("[SUB] ");
Serial.print(topic);
Serial.print(" → ");
Serial.println(msg);
if (String(topic) == TOPIC_BUZZER) {
if (msg == "ON") {
buzzerState = true;
digitalWrite(BUZZER_PIN, LOW);
Serial.println("🔔 Buzzer ON (remote)");
} else if (msg == "OFF") {
buzzerState = false;
digitalWrite(BUZZER_PIN, HIGH);
Serial.println("🔕 Buzzer OFF (remote)");
}
}
}
void connectWiFi() {
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
int retries = 0;
while (WiFi.status() != WL_CONNECTED && retries < 20) {
delay(500);
Serial.print(".");
retries++;
}
if (WiFi.status() != WL_CONNECTED) {
Serial.println("\n WiFi failed! Restarting...");
delay(1000);
ESP.restart();
}
Serial.println("\n WiFi connected! IP: " + WiFi.localIP().toString());
}
void connectMQTT() {
int retries = 0;
while (!mqtt.connected() && retries < 5) {
Serial.print(" Connecting to MQTT broker...");
if (mqtt.connect(MQTT_CLIENT, TOPIC_STATUS, 1, true, "offline")) {
Serial.println(" connected!");
mqtt.publish(TOPIC_STATUS, "online", true);
mqtt.subscribe(TOPIC_BUZZER);
} else {
Serial.print(" failed (rc=");
Serial.print(mqtt.state());
Serial.println("). Retry in 5s...");
delay(5000);
retries++;
}
}
if (!mqtt.connected()) {
Serial.println(" MQTT failed! Restarting...");
delay(1000);
ESP.restart();
}
}
Node 2 Code — MQ-9 Gas Sensor + LED
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
// ── WiFi Credentials ─────────────────────────────────────────────────────────
const char* WIFI_SSID = "YOUR_WIFI_SSID";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
// ── MQTT Broker ───────────────────────────────────────────────────────────────
const char* MQTT_BROKER = "broker.hivemq.com";
const int MQTT_PORT = 1883;
const char* MQTT_CLIENT = "esp01_node2_mq9";
// ── MQTT Topics ───────────────────────────────────────────────────────────────
const char* TOPIC_GAS = "home/node2/gas";
const char* TOPIC_STATUS = "home/node2/status";
const char* TOPIC_LED = "home/node2/led";
const char* TOPIC_BUZZER = "home/node2/buzzer";
// ── Pin Definitions ───────────────────────────────────────────────────────────
#define MQ9_DO_PIN 2 // GPIO2 — MQ-9 digital output
#define LED_PIN 0 // GPIO0 — LED (active LOW)
#define BUZZER_PIN 0 // GPIO0 — Buzzer shares same pin as LED
// ── Alert Timing ─────────────────────────────────────────────────────────────
#define BLINK_INTERVAL 500 // LED blinks every 500ms
#define BEEP_ON_TIME 100 // Buzzer ON duration (ms)
#define BEEP_OFF_TIME 900 // Buzzer OFF duration between beeps (ms)
// ── Sensor Read Interval ─────────────────────────────────────────────────────
#define READ_INTERVAL_MS 2000
#define SERIAL_BAUD 115200
// ── Objects ───────────────────────────────────────────────────────────────────
WiFiClient wifiClient;
PubSubClient mqtt(wifiClient);
// ── State Variables ───────────────────────────────────────────────────────────
unsigned long lastReadTime = 0;
bool previousGasState = false;
bool ledState = false;
bool gasAlertActive = false;
unsigned long lastBlink = 0;
unsigned long lastBeep = 0;
bool blinkState = false;
bool beepState = false;
void connectWiFi();
void connectMQTT();
void handleAlert();
void readAndPublish();
void mqttCallback(char* topic, byte* payload, unsigned int length);
void setup() {
Serial.begin(SERIAL_BAUD);
delay(200);
pinMode(MQ9_DO_PIN, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH);
Serial.println("\n=========================================");
Serial.println(" NODE 2 — MQ-9 + LED + MQTT");
Serial.println("=========================================");
Serial.println(" GPIO0 : LED ");
Serial.println(" GPIO2 : MQ-9 DO");
Serial.println(" Warming up MQ-9... (allow 20-30 sec)");
Serial.println("=========================================");
connectWiFi();
mqtt.setServer(MQTT_BROKER, MQTT_PORT);
mqtt.setCallback(mqttCallback);
connectMQTT();
}
void loop() {
if (!mqtt.connected()) connectMQTT();
mqtt.loop();
handleAlert();
unsigned long now = millis();
if (now - lastReadTime >= READ_INTERVAL_MS) {
lastReadTime = now;
readAndPublish();
}
}
void handleAlert() {
unsigned long now = millis();
if (gasAlertActive) {
if (now - lastBlink >= BLINK_INTERVAL) {
lastBlink = now;
blinkState = !blinkState;
digitalWrite(LED_PIN, blinkState ? LOW : HIGH);
}
if (!beepState && (now - lastBeep >= BEEP_OFF_TIME)) {
beepState = true;
lastBeep = now;
digitalWrite(BUZZER_PIN, LOW);
} else if (beepState && (now - lastBeep >= BEEP_ON_TIME)) {
beepState = false;
lastBeep = now;
digitalWrite(BUZZER_PIN, HIGH);
}
} else {
digitalWrite(LED_PIN, HIGH);
digitalWrite(BUZZER_PIN, HIGH);
blinkState = false;
beepState = false;
}
}
void readAndPublish() {
int rawState = digitalRead(MQ9_DO_PIN);
bool gasDetected = (rawState == LOW);
char payload[48];
snprintf(payload, sizeof(payload),
"{\"gas\":\"%s\",\"do\":%d}",
gasDetected ? "detected" : "normal", rawState);
mqtt.publish(TOPIC_GAS, payload, true);
Serial.print("[PUB] ");
Serial.print(TOPIC_GAS);
Serial.print(" → ");
Serial.println(payload);
if (gasDetected && !gasAlertActive) {
gasAlertActive = true;
Serial.println("🚨 ALERT: Gas detected! Blink + beep started.");
mqtt.publish(TOPIC_STATUS, "gas_alert", true);
} else if (!gasDetected && gasAlertActive) {
gasAlertActive = false;
Serial.println("✅ Air clean. Alert stopped.");
mqtt.publish(TOPIC_STATUS, "online", true);
}
previousGasState = gasDetected;
}
void mqttCallback(char* topic, byte* payload, unsigned int length) {
String msg;
for (unsigned int i = 0; i < length; i++) msg += (char)payload[i];
msg.trim();
Serial.print("[SUB] ");
Serial.print(topic);
Serial.print(" → ");
Serial.println(msg);
if (String(topic) == TOPIC_LED) {
if (msg == "ON") {
ledState = true;
if (!gasAlertActive) digitalWrite(LED_PIN, LOW);
} else if (msg == "OFF") {
ledState = false;
if (!gasAlertActive) digitalWrite(LED_PIN, HIGH);
}
}
if (String(topic) == TOPIC_BUZZER) {
if (msg == "ON") {
gasAlertActive = true;
Serial.println("🔔 Buzzer ON (remote) — blink + beep activated");
} else if (msg == "OFF") {
gasAlertActive = false;
digitalWrite(BUZZER_PIN, HIGH);
digitalWrite(LED_PIN, HIGH);
Serial.println("🔕 Buzzer OFF (remote) — alert cleared");
}
}
}
void connectWiFi() {
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
int retries = 0;
while (WiFi.status() != WL_CONNECTED && retries < 20) {
delay(500);
Serial.print(".");
retries++;
}
if (WiFi.status() != WL_CONNECTED) {
Serial.println("\n WiFi failed! Restarting...");
delay(1000);
ESP.restart();
}
Serial.println("\n WiFi connected! IP: " + WiFi.localIP().toString());
}
void connectMQTT() {
int retries = 0;
while (!mqtt.connected() && retries < 5) {
Serial.print(" Connecting to MQTT broker...");
if (mqtt.connect(MQTT_CLIENT, TOPIC_STATUS, 1, true, "offline")) {
Serial.println(" connected!");
mqtt.publish(TOPIC_STATUS, "online", true);
mqtt.subscribe(TOPIC_LED);
mqtt.subscribe(TOPIC_BUZZER);
} else {
Serial.print(" failed (rc=");
Serial.print(mqtt.state());
Serial.println("). Retry in 5s...");
delay(5000);
retries++;
}
}
if (!mqtt.connected()) {
Serial.println(" MQTT failed! Restarting...");
delay(1000);
ESP.restart();
}
}
07 Code Breakdown
Here is what each part of the code does. Read this after uploading to understand how the system works.
Libraries — Node 1
| Library | Purpose |
|---|---|
| ESP8266WiFi.h | Handles Wi-Fi connectivity for the ESP-01S. Used to connect the module to the local network and reach the HiveMQ broker over the internet. |
| PubSubClient.h | Enables MQTT communication. Used to connect to the broker, publish temperature data, and subscribe to the remote buzzer control topic. |
| OneWire.h | Implements the 1-Wire communication protocol required to read data from the DS18B20 sensor over a single data line. |
| DallasTemperature.h | Built on top of OneWire, this library simplifies reading temperature values from the DS18B20 sensor in Celsius and Fahrenheit. |
Libraries — Node 2
| Library | Purpose |
|---|---|
| ESP8266WiFi.h | Handles Wi-Fi connectivity for the ESP-01S module. |
| PubSubClient.h | Handles MQTT communication with the HiveMQ broker for publishing gas readings and subscribing to LED control commands. |
Key Functions — Node 1
| Function | Description |
|---|---|
| setup() | Initializes the serial monitor, sets the buzzer pin as output, starts the DS18B20 sensor, connects to Wi-Fi, and establishes the MQTT broker connection. |
| loop() | Continuously checks the MQTT connection and reconnects if dropped. Calls mqtt.loop() to process incoming messages and triggers readAndPublish() every 5 seconds using a non-blocking millis() timer. |
| readAndPublish() | Reads the current temperature from the DS18B20 sensor, formats the data as a JSON string containing both Celsius and Fahrenheit values, and publishes it to the broker. Also checks if the temperature exceeds 35°C and triggers the buzzer automatically. |
| mqttCallback() | Triggered automatically when a message is received on a subscribed topic. Handles remote buzzer control commands (ON / OFF) sent from the Node-RED dashboard. |
| connectWiFi() | Connects the ESP-01S to the specified Wi-Fi network. Retries every 500ms up to 20 attempts and restarts the module if the connection fails. |
| connectMQTT() | Connects to the HiveMQ broker with a Last Will and Testament (LWT) message set to "offline". Once connected, publishes "online" to the status topic and subscribes to the buzzer control topic. |
Key Functions — Node 2
| Function | Description |
|---|---|
| setup() | Initializes the serial monitor, sets the LED pin as output, connects to Wi-Fi, and establishes the MQTT broker connection. |
| loop() | Maintains MQTT connection, runs handleAlert() every cycle, and triggers readAndPublish() every 2 seconds. |
| readAndPublish() | Reads the digital output of the MQ-9 sensor. If gas is detected (LOW signal), it publishes {"gas":"detected"} to the broker and activates the gasAlertActive flag. If air is normal (HIGH signal), it publishes {"gas":"normal"} and clears the flag. |
| handleAlert() | Runs every loop cycle. Uses millis() timing to control non-blocking LED blink every 500ms during a gas alert — keeping the device responsive to MQTT messages even while alarming. |
| mqttCallback() | Triggered automatically when a message is received. Handles remote LED control commands (ON / OFF) sent from the Node-RED dashboard. |
| connectWiFi() | Connects the ESP-01S to the Wi-Fi network with automatic restart on failure after 20 retries. |
| connectMQTT() | Connects to the HiveMQ broker with an LWT message set to "offline". Once connected, publishes "online" to the status topic and subscribes to the LED control topic. |
General Program Workflow — Node 1
- Initialize serial communication, buzzer pin, and DS18B20 sensor on startup.
- Connect to Wi-Fi and establish MQTT broker connection.
- Subscribe to the buzzer control topic for remote commands.
- Every 5 seconds, read the temperature from the DS18B20 sensor.
- Publish the temperature as a JSON payload to the broker with retain flag.
- If temperature exceeds 35°C, activate the buzzer automatically.
- If a remote ON/OFF command is received, control the buzzer accordingly.
- If MQTT connection drops, automatically reconnect and re-subscribe.
General Program Workflow — Node 2
- Initialize serial communication and LED pin on startup.
- Connect to Wi-Fi and establish MQTT broker connection.
- Subscribe to the LED control topic for remote commands.
- Every 2 seconds, read the digital output of the MQ-9 gas sensor.
- Publish the gas status as a JSON payload to the broker with retain flag.
- If gas is detected, activate the gasAlertActive flag and blink the LED every 500ms.
- If air returns to normal, clear the alert and turn off the LED.
- If a remote LED ON/OFF command is received, control the LED accordingly.
- If MQTT connection drops, automatically reconnect and re-subscribe.
08 Testing and Calibration
After uploading, verify each of the following to confirm the system is working correctly.
Wi-Fi Connection Test
Open the Serial Monitor at 115200 baud after uploading. Both nodes should print a connected message followed by the assigned IP address. If the connection fails after 20 retries, the module will automatically restart.
MQTT Broker Connection Test
After Wi-Fi connects, the Serial Monitor should show a message confirming connection to the HiveMQ broker. The node will then publish "online" to its status topic and subscribe to its control topics. If the broker connection fails, the node retries up to 5 times before restarting.
Node 1 Temperature Sensor Test
The Serial Monitor for Node 1 should show temperature readings published every 5 seconds in the format {"tempC":27.50,"tempF":81.50}. If the DS18B20 is disconnected or not detected, the monitor will show a sensor_error message instead of temperature data.
Node 1 Buzzer Alert Test
To test the automatic temperature alert, carefully apply gentle heat near the DS18B20 sensor (such as holding it briefly between your fingers) until the reading exceeds 35°C. The buzzer should activate immediately. Remove the heat source and the buzzer should stop once the temperature drops back below 35°C.
Node 2 Gas Sensor Test
After the 20 to 30 second warm-up period, the Serial Monitor for Node 2 should show gas readings published every 2 seconds showing {"gas":"normal"}. To test detection, briefly expose the MQ-9 sensor to a combustible gas source such as a lighter (without igniting it). The LED should start blinking and the Serial Monitor should show the gas alert message.
Node-RED Dashboard Test
Open the Node-RED dashboard at http://localhost:1880/ui. Both nodes should appear as online in the status indicators. The temperature gauge for Node 1 should update every 5 seconds and the gas status text for Node 2 should update every 2 seconds. Test the remote control buttons by clicking the ON/OFF buttons for Node 1's buzzer and Node 2's LED and confirming the physical devices respond.
09 System Demonstration
The video below shows the complete working system. Use this to verify your output matches the expected behavior.
10 Conclusion
The system successfully demonstrates two autonomous sensor nodes independently monitoring environmental hazards and reporting to a unified cloud dashboard with minimal latency — typically under two seconds from sensor event to dashboard update.
The DS18B20's digital 1-Wire protocol proved far more robust than analog alternatives, delivering consistent readings without calibration. The MQ-9's digital threshold output simplified gas detection to a single digitalRead(), though the 20 to 30 second warm-up must be accounted for in deployment.
The non-blocking millis() pattern in Node 2's handleAlert() is a critical design decision: using delay() for blink and beep timings would block mqtt.loop() and cause the connection to drop during an alert — the worst possible moment to lose connectivity. The Last Will and Testament mechanism proved its value during testing: pulling power from a node immediately caused the dashboard status indicator to flip to "offline" within the broker's keep-alive window.
HiveMQ's free public broker is ideal for prototyping but unsuitable for production — any device that knows your topic strings can publish or subscribe. Moving to a private broker with TLS and authentication is the first production concern.
Possible Improvements and Future Enhancements
- Add a local OLED display (SSD1306 via I2C) on each node so current readings are visible without network access.
- Introduce SMS or push notification alerts via Twilio or Pushover Node-RED nodes so alerts reach the operator even when the dashboard is not open.
- Add a DHT22 to Node 1 to also report relative humidity alongside temperature.
11 References
- Espressif Systems — ESP8266 Technical Reference Manual
- Espressif — ESP8266 Arduino Core Board Package Documentation
- Maxim Integrated (Analog Devices) — DS18B20 Digital Thermometer Datasheet
- Zhengzhou Winsen Electronics — MQ-9B Toxic Gas Sensor Datasheet
- FTDI — FT232R USB UART IC Datasheet
- Nick O'Leary — PubSubClient Arduino Library Documentation
- Miles Burton — DallasTemperature Arduino Library Documentation
- HiveMQ — Public MQTT Broker Documentation
- HiveMQ — Web MQTT Client for Testing
- Node-RED Project — Official Documentation
- Node-RED Dashboard — node-red-dashboard Package Documentation
- Ngrok — Official Documentation
- Arduino — ESP8266WiFi Library Reference
- Canva — Online Design Tool, Used for Hardware Schematic and Wiring Diagram
- Paul Stoffregen — OneWire Arduino Library Documentation
12 Project Authors
- McRaven B. Batapa
- Joshua T. Colendres
