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

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

Wiring Schematic Diagram

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.

Wiring Schematic Diagram

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

  1. Place the ESP-01S Node 1 module on the breadboard.
  2. Connect the DS18B20 sensor VCC to the 3.3V rail, GND to the GND rail, and DQ to GPIO2.
  3. Place a 4.7kΩ resistor between the DS18B20 DQ pin and the 3.3V rail.
  4. Connect the active buzzer positive (+) to GPIO0 and negative (−) to GND.
  5. Connect one leg of the pushbutton to RST and the other leg to GND.
  6. Connect the ESP-01S VCC to the 3.3V rail and GND to the GND rail.
  7. Connect CH_PD (sometimes labeled EN) directly to the 3.3V rail — this pin must be HIGH for the ESP-01S to power on.
💡 Note: The 4.7kΩ pull-up resistor on the DS18B20 DQ line is required for the 1-Wire protocol to function. The buzzer operates on active LOW logic — LOW turns it ON, HIGH turns it OFF.

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

  1. Place the ESP-01S Node 2 module on the breadboard.
  2. 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.
  3. Connect the LED anode (+) to GPIO0 through a 220Ω current-limiting resistor and the cathode (−) to GND.
  4. Connect one leg of the pushbutton to RST and the other leg to GND.
  5. Connect the ESP-01S VCC to the 3.3V rail and GND to the GND rail.
  6. Connect CH_PD (sometimes labeled EN) directly to the 3.3V rail — this pin must be HIGH for the ESP-01S to power on.
⌛ Note: Allow the MQ-9 sensor a warm-up time of 20 to 30 seconds after powering on before readings are reliable.

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

  1. Place the MB102 power supply module at one end of the breadboard.
  2. Set the first jumper to 3.3V for the ESP-01S modules and DS18B20 sensor.
  3. Set the second jumper to 5V for the MQ-9 gas sensor heater.
  4. Connect the 12V DC adapter to the MB102 DC jack.
  5. Connect the FT232R to the ESP-01S only during firmware flashing — disconnect after upload is complete.
⚠️ Note: The ESP-01S operates at 3.3V only. Never connect it to the 5V rail — doing so will permanently damage the module.

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

  1. Open a web browser and go to the official Arduino website at arduino.cc/en/software.
  2. Download the latest version of the Arduino IDE compatible with your operating system.
  3. Run the installer after the download is complete.
  4. Follow the installation instructions and wait for the setup to finish.
  5. Open the Arduino IDE after installation.

Step 2 — Install ESP8266 Board Package

  1. Open the Arduino IDE.
  2. Go to File > Preferences.
  3. In the Additional Boards Manager URLs field, add the following link:
    http://arduino.esp8266.com/stable/package_esp8266com_index.json
  4. Click OK to save the settings.
  5. Go to Tools > Board > Boards Manager.
  6. Search for ESP8266.
  7. Install the ESP8266 board package by ESP8266 Community.
  8. 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
⚠️ Note: Make sure to select Generic ESP8266 Module and not NodeMCU or any other variant — the ESP-01S has limited flash memory and different boot pin behavior.

Step 3 — Install Required Libraries

  1. In the Arduino IDE, go to Sketch > Include Library > Manage Libraries.
  2. Search and install the following libraries one by one:
Library Author
PubSubClient Nick O'Leary
OneWire Paul Stoffregen
DallasTemperature Miles Burton
💡 Note: Wait for each library to finish installing before searching for the next one.

Step 4 — Configure Your Wi-Fi Credentials

  1. Open the Node 1 code in the Arduino IDE.
  2. Find these two lines near the top of the code:
Arduino / C++
const char* WIFI_SSID     = "YOUR_WIFI_SSID";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
  1. Replace YOUR_WIFI_SSID with your Wi-Fi network name.
  2. Replace YOUR_WIFI_PASSWORD with your Wi-Fi password.
  3. Repeat the same for the Node 2 code.
⚠️ Note: Both nodes must be connected to the same Wi-Fi network that has internet access to reach the HiveMQ broker.

Step 5 — Flash Node 1 (DS18B20 + Buzzer)

  1. Connect the FT232R USB to UART module to the Node 1 ESP-01S.
  2. Pull GPIO0 to GND before powering on to enter flash mode.
  3. Connect the FT232R to your PC via USB.
  4. Open the Node 1 code in the Arduino IDE.
  5. Go to Tools > Board and select Generic ESP8266 Module.
  6. Go to Tools > Port and select the correct COM port.
  7. Click the Verify button to compile the code and check for errors.
  8. After successful verification, click the Upload button.
  9. Wait for the upload to complete.
  10. Disconnect GPIO0 from GND and reset the module.
  11. Open the Serial Monitor at 115200 baud to verify Wi-Fi and MQTT connection.
✅ Expected output: The Serial Monitor should show the Wi-Fi connection, MQTT broker connection, and temperature readings being published every 5 seconds.

Step 6 — Flash Node 2 (MQ-9 + LED)

  1. Disconnect the FT232R from Node 1 and connect it to the Node 2 ESP-01S.
  2. Pull GPIO0 to GND before powering on to enter flash mode.
  3. Open the Node 2 code in the Arduino IDE.
  4. Go to Tools > Board and select Generic ESP8266 Module.
  5. Go to Tools > Port and select the correct COM port.
  6. Click the Verify button to compile the code and check for errors.
  7. After successful verification, click the Upload button.
  8. Wait for the upload to complete.
  9. Disconnect GPIO0 from GND and reset the module.
  10. Open the Serial Monitor at 115200 baud to verify Wi-Fi and MQTT connection.
  11. Wait 20 to 30 seconds for the MQ-9 sensor to warm up before testing gas detection.
✅ Expected output: The Serial Monitor should show the Wi-Fi connection, MQTT broker connection, and gas readings being published every 2 seconds.

Step 7 — Set Up Node-RED Dashboard

  1. Install Node-RED on your Windows PC by following the instructions at nodered.org.
  2. Open a terminal and run node-red to start the server.
  3. Open a browser and go to http://localhost:1880.
  4. Go to Manage Palette and install the node-red-dashboard package.
  5. 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.
  6. 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.
  7. Add a Change node after it that sets msg.payload to msg.payload.tempC, then connect it to a Gauge widget.
  8. Subscribe to home/node2/gas, set its MQTT In Output to "a parsed JSON object" as well.
  9. Add a Change node after it that sets msg.payload to msg.payload.gas, then connect it to a Text widget.
  10. 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.
  11. Add an MQTT Out node with topic home/node1/buzzer, connected to a Switch or Button widget for Node 1's buzzer.
  12. Add an MQTT Out node with topic home/node2/led, connected to a separate widget for Node 2's LED.
  13. 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.
  14. Click the red Deploy button to activate the flow.
  15. Open the dashboard at http://localhost:1880/ui and confirm both nodes appear as online.
💡 Note: The MQTT In node's default Output setting only passes the raw payload as text. Without setting it to "a parsed JSON object" and extracting the specific field with a Change node, the Gauge and Text widgets will display the entire JSON string instead of a clean number or status.
⚠️ Note: By default, Node-RED Dashboard buttons send a boolean (true/false) payload, not a string. The firmware checks for the exact text "ON" or "OFF" — if the Payload On/Off fields are left at their defaults, the button will visibly publish a message but the ESP-01S will never react, with no error shown anywhere.
📱 Mobile Access: Connect your phone to the same Wi-Fi network and open http://YOUR-PC-IP:1880/ui in your phone browser. Find your PC IP by running ipconfig in Command Prompt.

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

Arduino / C++
#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

Arduino / C++
#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

  1. Initialize serial communication, buzzer pin, and DS18B20 sensor on startup.
  2. Connect to Wi-Fi and establish MQTT broker connection.
  3. Subscribe to the buzzer control topic for remote commands.
  4. Every 5 seconds, read the temperature from the DS18B20 sensor.
  5. Publish the temperature as a JSON payload to the broker with retain flag.
  6. If temperature exceeds 35°C, activate the buzzer automatically.
  7. If a remote ON/OFF command is received, control the buzzer accordingly.
  8. If MQTT connection drops, automatically reconnect and re-subscribe.

General Program Workflow — Node 2

  1. Initialize serial communication and LED pin on startup.
  2. Connect to Wi-Fi and establish MQTT broker connection.
  3. Subscribe to the LED control topic for remote commands.
  4. Every 2 seconds, read the digital output of the MQ-9 gas sensor.
  5. Publish the gas status as a JSON payload to the broker with retain flag.
  6. If gas is detected, activate the gasAlertActive flag and blink the LED every 500ms.
  7. If air returns to normal, clear the alert and turn off the LED.
  8. If a remote LED ON/OFF command is received, control the LED accordingly.
  9. 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.

Common Issue: If Wi-Fi fails to connect, double-check that the WIFI_SSID and WIFI_PASSWORD in the code exactly match your network credentials including letter case.

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.

Common Issue: If MQTT fails to connect, check that your Wi-Fi network has active internet access. The HiveMQ broker at broker.hivemq.com requires internet connectivity.

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.

Common Issue: If the temperature reads -127°C or shows sensor_error, check that the 4.7kΩ pull-up resistor is correctly placed between the DS18B20 DQ pin and the 3.3V rail.

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.

Common Issue: If the buzzer does not sound, check that the buzzer positive (+) is connected to GPIO0 and negative (−) to GND. Confirm the buzzer is an active type — it should produce sound with DC voltage only.

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.

Common Issue: If the sensor always reads detected even in clean air, the MQ-9 has not finished warming up. Wait the full 30 seconds before testing. If it never detects gas, adjust the sensitivity potentiometer on the MQ-9 module.

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.

Common Issue: If the dashboard shows no data, confirm that the MQTT In nodes in Node-RED are subscribed to the correct topic strings and connected to broker.hivemq.com on port 1883.

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
Smart Fire Alarm: Dual ESP-01S Sensors with MQTT, Node-RED & Remote Monitoring via Ngrok
#ds18b20#esp-01s#iot#mq-9#mqtt#ngrok#node-red

Leave a comment

All comments are moderated before being published