Contents — 26 sections
Project Overview
A mesh of self-powered wireless nodes that smells and feels a wildfire in its first minutes — when a satellite still sees nothing and a lookout is still hours from noticing.
A wildfire is almost survivable in its first ten minutes and almost unstoppable an hour later. The whole game is early detection, and the tools we usually rely on are late: satellites revisit on a schedule and need a fire big and hot enough to show through cloud and canopy; watchtowers and cameras see smoke only once a plume has risen above the trees; a 112/emergency call needs a human who has already noticed. Down in the understory, though, a fire announces itself long before any of that — a sharp rise in temperature, a collapse in local humidity, and above all the smoke and combustion gases that pour off smouldering vegetation minutes before there are visible flames. This project puts cheap sensors down where the fire starts and networks them so that first chemical whisper becomes an alert.
Each node is a small, rugged, solar-powered box that samples the air for the signature of combustion — smoke particulate and carbon-monoxide-rich gases from a metal-oxide sensor — together with temperature and humidity, and watches for the combination that means fire rather than any single cue. That combination matters enormously: a hot afternoon is not a fire, a dust cloud is not a fire, but rising smoke gas AND rising temperature AND falling humidity together, appearing suddenly, is. By fusing the channels and looking at rate-of-change against each node's own learned baseline, the detector fires on real events and stays quiet through the daily weather, which is the difference between a system people trust and one they mute.
The other half of the design is the network. A single node covers a small patch, so detection at landscape scale means many nodes spread across a forest — and forests have no mains power and no Wi-Fi. The nodes therefore run on solar and talk over LoRa in a mesh, each relaying its neighbours' messages so an alert from deep in the trees hops node-to-node out to a gateway at the forest edge and onward to the fire service, with the node's location. It is candid about its limits — low-cost gas sensors are indicative, coverage depends on node density, and it complements rather than replaces satellites and lookouts — but a dense mesh of honest, fast, ground-level nodes buys the one thing wildfire response never has enough of: minutes.
What this project does
- Samples smoke/combustion gas, temperature and humidity at ground level
- Fuses the channels and fires on the combination that means fire, not single cues
- Detects by rate-of-change against each node's learned baseline to reject weather
- Relays alerts node-to-node over a LoRa mesh to a forest-edge gateway
- Reports each alert with the originating node's location
- Runs unattended for seasons on solar + battery
- Escalates a confirmed detection immediately to responders
Real-World Applications
| Setting | How it is used |
|---|---|
| High-risk forest and wildland-urban interface | Dense node coverage over fire-prone forest or the vulnerable edge where settlements meet wildland, buying response time for the highest-consequence areas. |
| Plantations and managed forestry | Protecting commercial timber and preventing a small ignition from destroying years of growth. |
| Protected areas and biodiversity reserves | Early alerts in remote reserves where no lookout exists and access is slow. |
| Peatland and agricultural-burn monitoring | Catching smouldering peat or escaped stubble fires that produce heavy smoke gas before open flame. |
Deployment contexts where a build of this kind earns its keep.
Features & Capabilities
- Ground-level chemical detection — minutes before smoke rises or satellites see
- Multi-cue fusion (smoke + heat + humidity drop) to reject false alarms
- Per-node adaptive baselines so normal weather does not trigger it
- LoRa mesh relaying for coverage deep in roadless, powerless forest
- Solar, rugged, season-long unattended operation
- Located alerts routed straight to the fire service
- Honest about coverage and sensor limits — a complement to satellites/lookouts
Difficulty, Time & Required Skills
| Attribute | Value |
|---|---|
| Difficulty level | Advanced |
| Estimated completion time | 14–22 hours |
| Indicative build cost | ₹4,200 – ₹5,800 per node |
| Primary discipline | Environment |
| Reference platform | ESP32 DevKit V1 (ESP-WROOM-32) |
Skills you should have (or will pick up)
- Reading smoke/gas (metal-oxide) sensors and interpreting them qualitatively
- Multi-sensor fusion and rate-of-change (baseline) detection
- Building a LoRa mesh with message relaying and deduplication
- Solar power design for season-long remote nodes
- Ruggedising electronics for outdoor, high-temperature environments
Bill of Materials
Every part below is commonly available from Indian and international hobby-electronics suppliers. Prices are indicative 2026 retail figures in Indian rupees and will drift — treat them as a budgeting guide, not a quotation.
| Component | Key specification | Qty | Approx. cost |
|---|---|---|---|
| ESP32 DevKit V1 (ESP-WROOM-32) Wi-Fi transmit bursts peak near 500 mA — size the regulator accordingly. | Dual-core Xtensa LX6 @ 240 MHz, 520 KB SRAM, 4 MB flash, Wi-Fi 802.11 b/g/n + BLE 4.2, 34 GPIO, 18× 12-bit ADC, 2× 8-bit DAC | 1 | ₹450 |
| MQ-2 combustible gas / smoke sensor Needs 24–48 h burn-in and a stable 5 V; the heater alone draws ~150 mA. | 300–10000 ppm LPG, propane, methane, hydrogen, smoke; analogue + digital output | 1 | ₹150 |
| MQ-135 air-quality sensor Not a true CO₂ sensor — calibrate against clean air (R0) before trusting ppm. | NH₃, NOx, benzene, smoke, CO₂ proxy, 10–1000 ppm, analogue output | 1 | ₹180 |
| DHT22 / AM2302 temperature + humidity sensor Needs a 4.7 kΩ pull-up on the data line and 2 s between reads. | −40 to +80 °C ±0.5 °C, 0–100 %RH ±2 %, 0.5 Hz sample rate, single-wire digital | 1 | ₹250 |
| BME280 pressure/humidity/temperature sensor Self-heating skews temperature by ~1 °C — read in forced mode, not continuous. | 300–1100 hPa ±1 hPa, 0–100 %RH ±3 %, −40 to +85 °C ±1 °C, 3.4 µA at 1 Hz | 1 | ₹420 |
| IR flame sensor module (YG1006) Sunlight and incandescent bulbs both trigger it — always confirm with a second sensor type. | 760–1100 nm, 60° detection cone, 0.8 m range, analogue + digital out | 1 | ₹70 |
| SX1278 LoRa 433 MHz module (Ra-02) Never power the radio without an antenna — the PA will destroy itself. | −148 dBm sensitivity, +20 dBm output, up to 10 km line of sight, SF7–SF12 | 1 | ₹480 |
| 20 W 12 V polycrystalline solar panel Rated watts assume 1000 W/m² — plan for 60–70 % of nameplate in real installs. | Vmp 17.5 V, Imp 1.14 A, Voc 21.6 V, 350 × 290 mm, aluminium frame | 1 | ₹1,200 |
| TP4056 Li-ion charger + DW01 protection Buy the version *with* protection ICs — the bare charger will over-discharge your cell. | 1 A programmable CC/CV charge to 4.2 V ±1 %, over-discharge and short protection | 1 | ₹45 |
| 18650 Li-ion cell 3400 mAh + holder Never charge below 0 °C; always use a protected cell or a BMS. | 3.7 V nominal, 4.2 V full, 3400 mAh, ~12.6 Wh, 2 C discharge | 1 | ₹450 |
| Rugged UV/heat-resistant enclosure Must let air reach the gas sensor but shelter the board | Vented for gas ingress, IP-rated against rain/dust, shades the electronics | 1 | ₹550 |
| High-temperature battery + protection Standard Li-ion degrades/fails in forest summer heat | LiFePO4 preferred for heat tolerance, with over-temperature cutoff | 1 | ₹700 |
| LoRa mesh gateway (edge) Shared across the whole mesh | One gateway at the forest edge with backhaul (cellular/Ethernet) to responders | 1 | ₹2,500 |
Estimated total: ₹7,445, excluding tools, shipping and consumables.
Tools and consumables
- Soldering iron (temperature controlled, 350 °C) with 0.8 mm 60/40 or lead-free solder
- Digital multimeter — continuity, DC volts and current ranges
- Wire strippers, flush cutters and a small set of precision screwdrivers
- Heat-shrink tubing and a heat gun (or a lighter, carefully)
- A laptop with a USB port and the toolchain listed above
Hardware Specifications
| Part | Specification | Supply | Interface | Reference |
|---|---|---|---|---|
| ESP32 DevKit V1 (ESP-WROOM-32) | Dual-core Xtensa LX6 @ 240 MHz, 520 KB SRAM, 4 MB flash, Wi-Fi 802.11 b/g/n + BLE 4.2, 34 GPIO, 18× 12-bit ADC, 2× 8-bit DAC | 3.3 V logic / 5 V USB | UART, SPI, I²C, I²S, CAN, PWM | Datasheet |
| MQ-2 combustible gas / smoke sensor | 300–10000 ppm LPG, propane, methane, hydrogen, smoke; analogue + digital output | 5 V (heater) | Analogue + comparator digital | Datasheet |
| MQ-135 air-quality sensor | NH₃, NOx, benzene, smoke, CO₂ proxy, 10–1000 ppm, analogue output | 5 V (heater) | Analogue | Datasheet |
| DHT22 / AM2302 temperature + humidity sensor | −40 to +80 °C ±0.5 °C, 0–100 %RH ±2 %, 0.5 Hz sample rate, single-wire digital | 3.3–6 V | 1-wire proprietary | Datasheet |
| BME280 pressure/humidity/temperature sensor | 300–1100 hPa ±1 hPa, 0–100 %RH ±3 %, −40 to +85 °C ±1 °C, 3.4 µA at 1 Hz | 1.7–3.6 V (module has 3.3 V LDO) | I²C (0x76/0x77) or SPI | Datasheet |
| IR flame sensor module (YG1006) | 760–1100 nm, 60° detection cone, 0.8 m range, analogue + digital out | 3.3–5 V | Analogue + digital | Datasheet |
| SX1278 LoRa 433 MHz module (Ra-02) | −148 dBm sensitivity, +20 dBm output, up to 10 km line of sight, SF7–SF12 | 3.3 V | SPI | Datasheet |
| 20 W 12 V polycrystalline solar panel | Vmp 17.5 V, Imp 1.14 A, Voc 21.6 V, 350 × 290 mm, aluminium frame | 12 V nominal | MC4 / screw terminals | Datasheet |
| TP4056 Li-ion charger + DW01 protection | 1 A programmable CC/CV charge to 4.2 V ±1 %, over-discharge and short protection | 4.5–5.5 V in | micro-USB / pads | Datasheet |
| 18650 Li-ion cell 3400 mAh + holder | 3.7 V nominal, 4.2 V full, 3400 mAh, ~12.6 Wh, 2 C discharge | 3.0–4.2 V | Holder / spot-welded tabs | Datasheet |
Consolidated electrical and interface specifications for every active part in the build.
Power Budget & Supply Sizing
Add up the typical active current of every part, then size the supply with at least 50 % headroom so transmit bursts and motor inrush never brown out the controller.
| Load | Supply rail | Typical current (mA) | Notes |
|---|---|---|---|
| ESP32 DevKit V1 (ESP-WROOM-32) | 3.3 V logic / 5 V USB | 160 | Wi-Fi transmit bursts peak near 500 mA — size the regulator accordingly. |
| MQ-2 combustible gas / smoke sensor | 5 V (heater) | 150 | Needs 24–48 h burn-in and a stable 5 V; the heater alone draws ~150 mA. |
| MQ-135 air-quality sensor | 5 V (heater) | 150 | Not a true CO₂ sensor — calibrate against clean air (R0) before trusting ppm. |
| DHT22 / AM2302 temperature + humidity sensor | 3.3–6 V | 1.5 | Needs a 4.7 kΩ pull-up on the data line and 2 s between reads. |
| BME280 pressure/humidity/temperature sensor | 1.7–3.6 V (module has 3.3 V LDO) | 0.4 | Self-heating skews temperature by ~1 °C — read in forced mode, not continuous. |
| IR flame sensor module (YG1006) | 3.3–5 V | 15 | Sunlight and incandescent bulbs both trigger it — always confirm with a second sensor type. |
| SX1278 LoRa 433 MHz module (Ra-02) | 3.3 V | 120 | Never power the radio without an antenna — the PA will destroy itself. |
| 20 W 12 V polycrystalline solar panel | 12 V nominal | 1140 | Rated watts assume 1000 W/m² — plan for 60–70 % of nameplate in real installs. |
| TP4056 Li-ion charger + DW01 protection | 4.5–5.5 V in | 1000 | Buy the version *with* protection ICs — the bare charger will over-discharge your cell. |
Summed typical draw is 2736.9 mA. With a 1.5× design margin the supply should deliver at least 4200 mA continuously at the stated rail voltage.
Software Requirements & Development Environment
Reference toolchain: Arduino IDE 2.3.x with the ESP32 board package 3.x (or PlatformIO on VS Code). Anything newer normally works; anything older may lack the board definitions used here.
- Install the Arduino IDE 2.3.x (or PlatformIO if you prefer a real editor and dependency locking).
- Add
https://espressif.github.io/arduino-esp32/package_esp32_index.jsonunder File → Preferences → Additional Board Manager URLs, then install esp32 from the Boards Manager. - Set the correct port under Tools → Port. On Linux add yourself to the
dialoutgroup:sudo usermod -aG dialout $USERand log out and back in. - Open the Serial Monitor at 115200 baud — every sketch here logs its state there.
- Keep File → Preferences → Show verbose output during: compilation switched on while you are debugging build errors.
Required libraries
| Library | Why it is needed | Install |
|---|---|---|
| WiFi (ESP32 core) bundled | Station/AP connection management for the ESP32. | Bundled with the ESP32 Arduino core |
| DHT sensor library 1.4.6 | Timing-critical driver for DHT11/DHT22. | Library Manager → "DHT sensor library" by Adafruit |
| Adafruit BME280 2.2.x | Compensation maths for the Bosch pressure/humidity/temperature sensor. | Library Manager → "Adafruit BME280 Library" |
| LoRa (sandeepmistry) 0.8.0 | SX127x radio configuration, packet TX/RX and callbacks. | Library Manager → "LoRa" by Sandeep Mistry |
| ArduinoJson 7.x | Zero-allocation JSON serialisation and parsing. | Library Manager → "ArduinoJson" by Benoit Blanchon |
| Preferences (NVS) bundled | Wear-levelled key/value storage in ESP32 flash for settings. | Bundled with the ESP32 core |
Block Diagram
The block diagram shows the functional decomposition of the system — what senses, what decides, what acts, and where the data ends up.
Circuit Diagram & Wiring
Every signal line in the build is shown below, followed by a pin-by-pin connection table you can work through with a multimeter in hand.
| Peripheral | Peripheral pin | Controller pin | Signal |
|---|---|---|---|
| MQ-2 (smoke) | AOUT | GPIO 34 (ADC) | Smoke/combustible gas |
| MQ-135 (gas) | AOUT | GPIO 35 (ADC) | CO/VOC combustion gases |
| DHT22 / BME280 | DATA / I²C | GPIO 4 / 21-22 | Temp + humidity |
| Flame sensor | DOUT | GPIO 27 | IR flame (line-of-sight confirm) |
| LoRa SX1276 | SCK/MISO/MOSI | GPIO 18/19/23 | SPI mesh radio |
| LoRa SX1276 | NSS/RST/DIO0 | GPIO 5/14/2 | Chip-select, reset, IRQ |
| TP4056 | OUT | VIN / 3V3 reg | Solar-charged supply |
| Solar panel | +/– | TP4056 IN | 6 V panel → charger |
Wire one row at a time and tick it off — most "it does not work" reports trace back to a single swapped pair.
Wiring explanation
- Vent the enclosure so outside air reaches the gas sensors, but shade the electronics and battery from direct sun and the worst heat.
- Metal-oxide gas sensors have a heated element that draws steady current and needs warm-up; budget for it and keep its heater noise off the analogue grounds.
- Place the temperature/humidity sensor in the same vented airflow so its readings represent the air the gas sensors sample.
- Mount the flame sensor with a clear line of sight where possible; treat it as confirmation, not primary detection, since it needs direct view of flame.
- Keep the LoRa antenna vertical and as high as the mounting allows for mesh range under canopy.
System Architecture
Read the stack from the bottom up: physical hardware, the firmware that drives it, the transport that moves data off the device, and the software a human actually looks at.
Working Principle
The detector wins on physics of timing. A ground fire begins as smouldering combustion in leaf litter and undergrowth, and long before there are flames tall enough to see or hot enough for a satellite's thermal band, it is emitting a plume of smoke particulate and combustion gases — carbon monoxide and a soup of volatile organics — right at the height where our nodes sit. It also locally spikes the temperature and drives down the relative humidity as it heats and dries the air around it. Detecting these near the source, in the first minutes, is inherently earlier than any method that waits for the fire to grow large enough to be visible from above or from a distance.
The central design principle is multi-cue fusion, because no single sensor can tell fire from ordinary environmental variation. A metal-oxide gas sensor rises on a fire — but also on a passing vehicle's exhaust, a nearby cooking fire, or its own drift. Temperature rises on a fire — but also every sunny afternoon. Humidity falls in a fire — but also in a dry wind. Each cue alone would either false-alarm constantly or miss real fires depending on where you set the threshold. Fused, they become specific: it is the coincidence of rising combustion gas, rising temperature and falling humidity, arriving together and quickly, that is the fingerprint of fire and almost nothing else. The node computes a combined fire score from all channels rather than trusting any one.
Because the cheap gas sensors drift and every site has a different "normal", detection is done by rate-of-change against a per-node adaptive baseline, not fixed thresholds. Each node continuously learns its own slow background for each channel; an alarm needs a departure that is fast and large relative to that baseline and the channel's normal noise. This makes a node in a humid valley and one on a dry ridge each judge fire by its own normal, absorbs slow sensor drift automatically, and keys on the sudden coincident change that fire produces rather than any absolute value. A flame sensor, where it has line of sight, adds a final confirmation channel — direct evidence of flame that sharply raises confidence when present, though its short range and need for a clear view keep it a confirmer rather than the primary detector.
Detection at landscape scale is a networking problem as much as a sensing one. One node protects a small radius, so real coverage means a dense field of nodes — and forests are exactly where there is no power and no cellular. The answer is solar nodes on a LoRa mesh: each node not only sends its own messages but relays its neighbours', so an alert originating deep in roadless terrain hops from node to node until it reaches a gateway at the forest edge with backhaul to the fire service. Messages carry the originating node's ID and location and a hop count, and the mesh deduplicates and rate-limits relays so one alert does not storm the network. The system is deliberately honest about the trade-offs — coverage is only as good as node density, and a low-cost gas sensor is indicative not analytical — but as a fast, ground-truth complement to satellites and lookouts, a mesh like this delivers the minutes that decide whether a fire is a footnote or a catastrophe.
The maths behind it
Per-node adaptive baseline and anomaly
For each channel x (smoke, gas, temp, −RH):
base ← base + α·(x − base) (α small, slow)
var ← 0.98·var + 0.02·(x − base)^2
z_x = (x − base) / (sqrt(var) + ε)
z_x is how many "normals" this channel has jumped.
Using −RH means a humidity DROP contributes positively,
aligning all cues so a fire pushes every z_x upward.
Fused fire score
Combine the standardised cues (require coincidence):
score = w1·z_smoke + w2·z_gas + w3·z_temp + w4·z_negRH
+ FLAME_BONUS·flame_confirmed
Alarm if score > S_thresh sustained over N reads.
Weights w emphasise the gas/smoke channels; the flame
bonus sharply raises confidence when a flame is seen.
Coincidence (several z high together) is what fire looks
like — one channel alone rarely crosses S_thresh.
Mesh relay with deduplication
Each alert packet: {node_id, lat, lon, score, msg_id, hops}
on receive:
if msg_id already seen → drop (dedup)
else record msg_id; if hops < HOP_MAX:
hops++ ; rebroadcast after random backoff
Random backoff avoids collisions; HOP_MAX bounds flooding;
dedup stops a message circulating forever. The alert walks
outward to the edge gateway node by node.
Program Flowchart
The firmware is a single cooperative loop. Nothing blocks for long, so networking, sensing and the user interface all stay responsive.
Assembly Instructions
Build on a breadboard first and only commit to solder once the whole system has run for an hour without a fault.
Build the vented, heat-tolerant node
House the electronics in a rugged, UV-resistant enclosure that is vented so outside air reaches the gas sensors while the board and battery are shaded from direct sun.
Use a heat-tolerant battery (LiFePO4) with over-temperature protection — forest summer heat destroys ordinary Li-ion.
Fit and warm the sensors
Mount the MQ-2/MQ-135 in the vented airflow and allow their heaters to stabilise (a warm-up period) before trusting readings. Place the temperature/humidity sensor in the same airflow, and the flame sensor with a clear view where possible.
Set up solar and the mesh radio
Angle the solar panel to the sun; size the panel and battery for the shortest winter days and canopy shade. Mount the LoRa antenna high and vertical for mesh range, and place gateways at the forest edge with backhaul.
Step-by-Step Implementation Guide
Work through these in order. Each step ends in something you can observe, so a failure is always localised to the step you just finished.
Learn baselines and standardise cues
Let each node learn a slow baseline and variance per channel, and standardise each reading into a z-score of how far it has jumped from that node's own normal.
Fuse into a fire score with coincidence
Combine the standardised cues into a single score that only crosses threshold when several channels rise together, and require the score to persist to reject transient spikes.
cppfire-fusion.inostruct Chan { float base, var; bool primed; }; Chan smoke, gas, temp, humid; float zscore(Chan &c, float x) { if (!c.primed) { c.base = x; c.var = 1; c.primed = true; return 0; } float d = x - c.base; c.var = 0.98f * c.var + 0.02f * d * d; c.base += 0.02f * d; // slow per-node baseline return d / (sqrtf(c.var) + 1e-3f); } // Fuse cues; humidity is entered as its NEGATIVE so a drop reads positive. float fireScore(float smk, float g, float t, float rh, bool flame) { float zs = zscore(smoke, smk); float zg = zscore(gas, g); float zt = zscore(temp, t); float zh = zscore(humid, -rh); // humidity DROP → positive z float score = 1.2f*zs + 1.2f*zg + 0.9f*zt + 0.9f*zh; if (flame) score += 3.0f; // direct flame confirmation return score; } // Alarm needs coincidence AND persistence, not one loud channel. bool fireConfirmed(float score, uint8_t &nHigh) { if (score > 5.0f) nHigh++; else nHigh = 0; return nHigh >= 3; // sustained multi-cue rise }return d / (sqrtf(c.var) + 1e-3f)Each channel is expressed as how many of its own normal fluctuations it has jumped, so drift and site differences wash out and only genuine departures count.float zh = zscore(humid, -rh)Feeding negative humidity makes a humidity drop contribute positively, aligning all four cues so a real fire pushes every one of them up together.if (flame) score += 3.0fA line-of-sight flame detection adds a large bonus — direct evidence of fire that sharply raises confidence when available.return nHigh >= 3Confirmation requires the fused score to stay high for several reads, so a single sensor glitch or a passing exhaust puff cannot trip a landscape-scale alert.Alert into the mesh with location
On a confirmed detection, emit a located alert packet into the LoRa mesh with a unique message id; relay neighbours' alerts with deduplication and a hop limit so every alert reaches the edge gateway once.
Complete Source Code
The listing below is complete and compiles as written — there are no elided sections. Read the annotations under each block before you upload it.
/* ═══════════════════════════════════════════════════════════════
Forest Fire Early Detector — ESP32, smoke/gas/temp/RH, LoRa mesh
Fuses ground-level combustion cues against per-node baselines,
confirms fire by coincidence + persistence, and relays located
alerts through a solar LoRa mesh to a forest-edge gateway.
══════════════════════════════════════════════════════════════════ */
#include <DHT.h>
#include <LoRa.h>
#include <SPI.h>
#include <Preferences.h>
#include <math.h>
#define PIN_SMOKE 34
#define PIN_GAS 35
#define PIN_DHT 4
#define PIN_FLAME 27
#define LORA_CS 5
#define LORA_RST 14
#define LORA_DIO0 2
#define HOP_MAX 6
#define SLEEP_S 60 // 1 min — fast enough for early detection
DHT dht(PIN_DHT, DHT22);
Preferences prefs;
const uint16_t NODE_ID = 1;
float NODE_LAT, NODE_LON;
RTC_DATA_ATTR struct C { float base, var; bool primed; }
cSmoke, cGas, cTemp, cHumid;
RTC_DATA_ATTR uint32_t msgCounter = 0;
RTC_DATA_ATTR uint32_t seenIds[16]; RTC_DATA_ATTR uint8_t seenN = 0;
float z(struct C &c, float x) {
if (!c.primed) { c.base = x; c.var = 1; c.primed = true; return 0; }
float d = x - c.base;
c.var = 0.98f * c.var + 0.02f * d * d;
c.base += 0.02f * d;
return d / (sqrtf(c.var) + 1e-3f);
}
bool seen(uint32_t id) {
for (int i = 0; i < seenN; i++) if (seenIds[i] == id) return true;
seenIds[seenN % 16] = id; seenN++;
return false;
}
void sendAlert(float score) {
uint32_t id = ((uint32_t)NODE_ID << 16) | (++msgCounter & 0xFFFF);
char pkt[160];
snprintf(pkt, sizeof pkt,
"{\"t\":\"fire\",\"node\":%u,\"lat\":%.5f,\"lon\":%.5f,"
"\"score\":%.1f,\"id\":%lu,\"hops\":0}",
NODE_ID, NODE_LAT, NODE_LON, score, (unsigned long)id);
LoRa.beginPacket(); LoRa.print(pkt); LoRa.endPacket();
}
// Relay any alert we hear (dedup + hop limit) so it walks to the edge.
void relayIfNeeded() {
int sz = LoRa.parsePacket();
if (!sz) return;
char buf[200]; int n = 0;
while (LoRa.available() && n < 199) buf[n++] = LoRa.read();
buf[n] = 0;
// (a real build parses JSON; shown conceptually)
uint32_t id; int hops;
if (parseAlert(buf, id, hops)) {
if (seen(id)) return; // dedup
if (hops < HOP_MAX) {
delay(random(20, 200)); // random backoff vs collisions
char out[210]; bumpHops(buf, out); // hops+1
LoRa.beginPacket(); LoRa.print(out); LoRa.endPacket();
}
}
}
void setup() {
Serial.begin(115200);
pinMode(PIN_FLAME, INPUT);
dht.begin();
prefs.begin("fire", true);
NODE_LAT = prefs.getFloat("lat", 0); NODE_LON = prefs.getFloat("lon", 0);
prefs.end();
analogSetPinAttenuation(PIN_SMOKE, ADC_11db);
analogSetPinAttenuation(PIN_GAS, ADC_11db);
SPI.begin();
LoRa.setPins(LORA_CS, LORA_RST, LORA_DIO0);
LoRa.begin(433E6);
LoRa.setSpreadingFactor(10);
// relay anything heard while we were asleep/awake
relayIfNeeded();
// ── read + fuse this node's cues ──
float smk = analogRead(PIN_SMOKE);
float g = analogRead(PIN_GAS);
float t = dht.readTemperature();
float rh = dht.readHumidity();
bool flame = digitalRead(PIN_FLAME) == LOW;
float score = 1.2f*z(cSmoke, smk) + 1.2f*z(cGas, g)
+ 0.9f*z(cTemp, t) + 0.9f*z(cHumid, -rh)
+ (flame ? 3.0f : 0.0f);
static uint8_t nHigh;
if (score > 5.0f) nHigh++; else nHigh = 0;
if (nHigh >= 3) sendAlert(score); // confirmed: coincidence+persist
esp_sleep_enable_timer_wakeup((uint64_t)SLEEP_S * 1000000ULL);
esp_deep_sleep_start();
}
void loop() {} // deep sleep restarts setup()
Configuration & Calibration
Configuration steps
- Set each node's location (lat/lon) in flash so alerts are geolocated.
- Tune the cue weights, the score threshold and the persistence count from field trials in your vegetation type.
- Set the sampling interval (1 min for fast detection) and the mesh HOP_MAX to your network diameter.
- Choose the region-legal LoRa frequency and place edge gateways with reliable backhaul to responders.
Calibration procedure
An uncalibrated sensor produces confident, precise, wrong numbers. Do this once per physical unit and record the constants.
Baseline settling
At deployment, let baselines learn for hours to a day (sensors warmed up, weather sampled) before enabling alerts so a node does not trip on its own start-up.
Controlled smoke test
With authorisation and safety, introduce a small controlled smoke source near a node and confirm the fused score rises and confirms while single-channel noise does not.
Mesh range and relay
Verify neighbour-to-neighbour range under canopy and that a test alert from the deepest node reaches the edge gateway with sensible hop counts.
Network Architecture & Connectivity
Communication protocol
Nodes sample every minute and stay silent unless a fire is confirmed; alerts are small located packets that flood outward through the mesh with deduplication and a hop limit, reaching the edge gateway and then responders in seconds.
| Topic / endpoint | Direction | Payload |
|---|---|---|
fire/mesh/alert | node → gateway | located fire alert (node, lat/lon, score, id, hops) |
fire/node/health | node → gateway | battery, baselines, RSSI (periodic) |
fire/gateway/dispatch | gateway → responders | geolocated dispatch with confidence |
Message contract between the device and the broker.
Cloud platform configuration
An alert server geolocates each detection, correlates nearby nodes (several nodes confirming raises confidence), and dispatches to the fire service with a map, while a health map tracks battery and connectivity of every node.
Dashboard setup
A forest map of node health with instant red alerting, showing the originating node, fused score, relay path and any corroborating neighbours.
Mobile app integration
Immediate located push/SMS to responders on a confirmed detection, with confidence raised when multiple nodes agree.
Security considerations
- Authenticate and sign alerts so a false fire cannot be injected to waste response resources.
- Rate-limit and dedup relays so the mesh cannot be flooded, accidentally or maliciously.
- Monitor node health so a cluster going silent (possibly burned or failed) is itself a signal.
Testing Procedure & Expected Output
Test from the bottom up. Confirm power, then each sensor in isolation, then the integrated loop — the first failing step tells you exactly where to look.
| Test | What you should see |
|---|---|
| Heat one channel only (e.g. warm the node) | Score stays below threshold — single cue is not fire |
| Introduce smoke + heat + dry air together | Fused score rises, persists, and an alert is emitted |
| Trigger the flame sensor with a safe flame in view | Score jumps via the flame bonus; confirmation faster |
| Inject a test alert at the far node | It relays hop-by-hop to the edge gateway; each node relays once |
| Rebroadcast a duplicate id | Dedup drops it; no relay storm |
| Run a solar season cycle in heat | Battery survives heat; node keeps sampling and relaying |
Bench-test checklist. If a row fails, stop and fix it before moving on.
Expected output
The responder dashboard shows a map of nodes (green healthy, red alerting) and, on a detection, the originating node's location, fire score and the relay path out to the gateway.
{
"t": "fire",
"node": 47,
"lat": 30.41822,
"lon": 78.09143,
"score": 7.8,
"id": 3080193,
"hops": 3
}
Here node 47 deep in the forest has confirmed a fire (fused score 7.8) and its located alert has reached the edge gateway in three hops — a geolocated warning delivered in the fire's first minutes.
Troubleshooting: Common Errors & Fixes
Performance Optimisation
- Deep-sleep between one-minute samples; the gas-sensor heaters are the main continuous draw, so manage warm-up carefully.
- Keep nodes silent unless confirming — the mesh should carry almost no traffic until a real event.
- Persist baselines in RTC memory so detection survives sleep without re-priming.
- Bound mesh flooding with dedup, random backoff and a hop limit so alerts propagate fast without storms.
- Replace every
delay()with amillis()comparison — blocking delays are the single most common cause of dropped readings. - Sample sensors on a fixed cadence and publish on a slower one; you almost never need to transmit at the sampling rate.
- Move networking into its own FreeRTOS task so a slow DNS lookup cannot stall the control loop.
- Use
uint8_t/uint16_twhere the range allows; on an 8-bit AVR a 32-bit add costs four times as much. - Batch several samples into one MQTT publish. Radio time, not CPU time, dominates the energy budget.
- Set the MQTT keep-alive to a value that matches your reporting interval so the broker does not churn reconnections.
- For battery builds use deep sleep between samples: an ESP32 drops from ~160 mA awake to about 10 µA asleep, which is the difference between days and months of runtime.
Safety Precautions
- This is a complement to satellites, cameras and lookouts — coverage depends on node density and it must not be the sole line of defence.
- Design nodes and batteries for extreme heat with over-temperature protection; a node must not itself become an ignition or failure risk.
- Conduct any smoke/flame testing with authorisation and full fire-safety precautions.
- Ensure alerts reach a real dispatch path with human confirmation before mobilising resources.
- Lithium cells vent and burn when abused. Only use protected cells or a proper BMS, never charge below 0 °C, and never leave a charging pack unattended on a wooden desk.
- MQ-series sensors run a hot element. They get genuinely hot, need ventilation, and must never be enclosed in a sealed plastic box.
- Never power an RF module without its antenna fitted — the reflected power destroys the output stage. Check your local licence-free band and duty-cycle limits before transmitting.
- Wear eye protection when soldering or cutting, and solder in a ventilated space — rosin flux fumes are a respiratory irritant.
- Power the circuit through a bench supply with a current limit while you are testing. A 300 mA limit turns a wiring mistake into a beep instead of a dead board.
- Disconnect power before changing any wiring. Hot-plugging a sensor onto a live bus is the fastest way to lose a controller.
Maintenance
- Replace ageing gas sensors and re-verify detection with a controlled test each season before the fire season.
- Check battery health and over-temperature protection; heat degrades cells fastest.
- Verify mesh connectivity and fill coverage gaps with additional relay nodes.
- Keep solar panels clear of dust and canopy debris; a starved node is a blind spot.
- Re-check every screw terminal and header after the first week — thermal cycling loosens connections that felt tight on day one.
- Log pack voltage. When resting voltage after a full charge drops below about 4.0 V, the cell is near end of life — replace it.
- Wash the panel every few weeks in dusty conditions; a visible dust film costs 15–25 % of the harvest.
- Keep the broker and dashboard containers patched, and rotate device credentials at least once a year.
- Recalibrate at the interval given in the calibration section, and keep the constants in a text file next to the firmware — not only in flash.
- Keep a short logbook of firmware versions and what changed. Six months later you will not remember why that constant is 1.083.
Future Improvements & Upgrades
A working v1 is a platform, not a finish line. These are the upgrades that add the most capability for the least rework.
- Add low-cost thermal or optical smoke imaging on select nodes for visual confirmation.
- Correlate node detections with weather (wind, dryness indices) to predict spread direction with the alert.
- Machine-learn the fusion weights per vegetation type from labelled fire/no-fire episodes.
- Add satellite backhaul on gateways for forests beyond any cellular coverage.
- Design a proper PCB. Once the breadboard version has run for a month, moving to a two-layer board removes the intermittent-contact failures that dominate prototype faults.
- Add over-the-air firmware updates so you never have to physically reach a deployed node again.
- Add persistent local storage (microSD or the on-chip flash) so a network outage does not create a hole in your data.
- Move configuration out of the source: a captive-portal setup page or a JSON config file makes the build reusable without a recompile.
- Add a battery and solar option so the unit survives a power cut and can be sited away from a socket.
- Write a small test harness that feeds synthetic sensor values through the decision logic, so you can validate thresholds without physically triggering the event.
Frequently Asked Questions
References & Learning Resources
These are the primary sources worth reading in full. Manufacturer datasheets always outrank forum posts when the two disagree.