Ajdin Nakičević
Published © GPL3+

Smart WildFire Extinguisher

A fire-monitoring and crowdsourcing tool that will allow local fire managers to respond to wildfires.

AdvancedFull instructions provided20 hours5,580

Things used in this project

Hardware components

Arduino Mini 05
Arduino Mini 05
×1
smoke sensor
×1
Humidity and Temperature Sensor
Adafruit Humidity and Temperature Sensor
×1
Temperature Sensor
Temperature Sensor
×1
GPS Module (Generic)
×1
Breadboard (generic)
Breadboard (generic)
×1
Jumper wires (generic)
Jumper wires (generic)
×1
Relay (generic)
×1
ESP8266 ESP-12E
Espressif ESP8266 ESP-12E
×1
nRF24 Module (Generic)
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

object filled with ABC powder

hollow object, milled with a CNC machine, filled with ABC powder
after the detection of fire, the powder will be dispersed to extinguish it

object filled with ABC powder

Schematics

schematics

Code

fire recognition algorithm

C#
the following code detects fire on a picture
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Emgu.CV;
using Emgu.CV.Structure;
using Emgu.CV.Cvb;
using Emgu.CV.CvEnum;

namespace Hakaton
{
    public partial class Form1 : Form
    {
        private VideoCapture videoCapture;
        private bool detectFire;
        private bool frameSkip = true;

        private double redThreshold = 220;
        private double whiteThreshold = 220;
        private int frameInterval = 1000 / 30;

        private Timer fpsTimer;

        public Form1()
        {
            InitializeComponent();
            Init();
        }

        private void Init()
        {
            picPreview.SizeMode = PictureBoxSizeMode.StretchImage;
            picRedFilter.SizeMode = PictureBoxSizeMode.StretchImage;
            picFinal.SizeMode = PictureBoxSizeMode.StretchImage;

            trbSeek.Enabled = false;
        }

        private void OpenVideo(string filename)
        {
            videoCapture = new VideoCapture(filename);

            int frameCount = (int) videoCapture.GetCaptureProperty(CapProp.FrameCount);
            trbSeek.Minimum = 0;
            trbSeek.Maximum = frameCount;
            trbSeek.Value = 0;
            trbSeek.Enabled = true;

            fpsTimer = new Timer();
            fpsTimer.Interval = frameInterval;
            fpsTimer.Tick += ProcessFrame;
            fpsTimer.Start();
        }

        private void OpenCamera()
        {
            videoCapture = new VideoCapture();
            trbSeek.Enabled = false;
            frameSkip = false;

            Application.Idle += ProcessFrame;
        }

        private void ProcessFrame(object sender, EventArgs e)
        {
            DateTime methodStart = DateTime.Now;

            Image<Bgr, Byte> redFiltered = null;
            Image<Bgr, Byte> ycbcrFiltered = null;
            Image<Gray, Byte> blobImage = null;

            Image<Bgr, Byte> rawFrame = videoCapture.QueryFrame().ToImage<Bgr, Byte>();
            rawFrame = rawFrame.Resize(320, 240, Emgu.CV.CvEnum.Inter.Cubic);
            rawFrame._EqualizeHist();
            
            if (detectFire)
            {
                redFiltered = redTreshhold(rawFrame);
                ycbcrFiltered = yCbCrThreshold(redFiltered);
                blobImage = binaryTreshold(ycbcrFiltered);

                CvBlobs blobs = new CvBlobs();
                CvBlobDetector blobDetector = new CvBlobDetector();
                uint blobCount = blobDetector.Detect(blobImage, blobs);

                int minArea = (int)(rawFrame.Width * rawFrame.Height * 0.002);

                foreach (KeyValuePair<uint, CvBlob> blobPair in blobs)
                {
                    if(blobPair.Value.Area > minArea)
                    {
                        Rectangle rect = blobPair.Value.BoundingBox;
                        rawFrame.Draw(rect, new Bgr(0, 255, 0), 5);
                    }
                }
            }

            picPreview.Image = rawFrame.Bitmap;
            if(detectFire)
            {
                picRedFilter.Image = redFiltered.Bitmap;
                picFinal.Image = blobImage.Bitmap;
            }
            else
            {
                picRedFilter.Image = null;
                picFinal.Image = null;
            }

            if(frameSkip)
            {
                int timePassed = (DateTime.Now - methodStart).Milliseconds;
                int framesToSkip = timePassed / frameInterval;
                for (int i = 0; i < framesToSkip; i++)
                    videoCapture.QueryFrame();

            }

            int currentFrame = (int) videoCapture.GetCaptureProperty(CapProp.PosFrames);
            int frameCount = (int) videoCapture.GetCaptureProperty(CapProp.FrameCount);
            if(currentFrame != -1  && frameCount != -1)
            {
                trbSeek.Value = currentFrame;
                if (currentFrame == frameCount)
                    CloseVideo();
            }
        }

        private void CloseVideo()
        {
            fpsTimer.Stop();
            picPreview.Image = null;
            picRedFilter.Image = null;
            picFinal.Image = null;
        }

        Image<Gray, Byte> binaryTreshold(Image<Bgr, Byte> originalImage)
        {
            Image<Gray, Byte> newImage = new Image<Gray, byte>(originalImage.Width, originalImage.Height);

            Bgr black = new Bgr(0, 0, 0);
            for (int y = 0; y < originalImage.Height; y++)
            {
                for (int x = 0; x < originalImage.Width; x++)
                {
                    if (originalImage[y, x].Equals(black))
                        newImage[y, x] = new Gray(0);
                    else
                        newImage[y, x] = new Gray(255);
                }
            }

            return newImage;
        }

        Image<Bgr, Byte> redTreshhold(Image<Bgr, Byte> originalImage)
        {
            Image<Bgr, Byte> newImage = new Image<Bgr, byte>(originalImage.Width, originalImage.Height);

            for (int y = 0; y < originalImage.Height; y++)
            {
                for (int x = 0; x < originalImage.Width; x++)
                {
                    double r = originalImage[y, x].Red;
                    double g = originalImage[y, x].Green;
                    double b = originalImage[y, x].Blue;

                    bool isFire = false;

                    if (r > g && r > b)
                        if (r > redThreshold)
                            isFire = true;

                    if (r > whiteThreshold && g > whiteThreshold && b > whiteThreshold)
                        isFire = true;

                    if (isFire)
                        newImage[y, x] = originalImage[y, x];
                    else
                        newImage[y, x] = new Bgr(0, 0, 0);
                }
            }

            return newImage;
        }

        Image<Bgr, Byte> yCbCrThreshold(Image<Bgr, Byte> originalImage)
        {
            Image<Bgr, Byte> newImage = new Image<Bgr, byte>(originalImage.Width, originalImage.Height);

            for (int Y = 0; Y < originalImage.Height; Y++)
            {
                for (int X = 0; X < originalImage.Width; X++)
                {
                    double rRaw = originalImage[Y, X].Red;
                    double gRaw = originalImage[Y, X].Green;
                    double bRaw = originalImage[Y, X].Blue;

                    double r = rRaw / 255;
                    double g = gRaw / 255;
                    double b = bRaw / 255;

                    double y = 0.299 * r + 0.587 * g + 0.114 * b;
                    double cB = -0.168736 * r + -0.331264 * g + 0.500 * b;
                    double cR = 0.500 * r + -0.418688 * g + -0.081312 * b;

                    bool isFire = false;

                    if(y >= cR  &&  cR >= cB)
                    {
                        double crcb = cR - cB;
                        double ycb = y - cB;
                        if (!((crcb >= -0.1 && ycb >= -0.1 && ycb <= 0.3) || (crcb >= 0 && crcb <= 0.4 && ycb >= 0 && ycb <= 0.8)))
                            isFire = true;
                    }

                    if (isFire)
                        isFire = !(cR - cB > -0.1 && y - cB > -0.1 && y - cB <= 0.6);
                    
                    if (isFire)
                        newImage[Y, X] = originalImage[Y, X];
                    else
                        newImage[Y, X] = new Bgr(0, 0, 0);
                }
            }

            return newImage;
        }

        private void chkDetect_CheckedChanged(object sender, EventArgs e)
        {
            detectFire = chkDetect.Checked;
        }

        private void trbSeek_Scroll(object sender, EventArgs e)
        {
            int frameIndex = trbSeek.Value;
            videoCapture.SetCaptureProperty(CapProp.PosFrames, frameIndex);
        }

        private void chkFrameSkip_CheckedChanged(object sender, EventArgs e)
        {
            frameSkip = chkFrameSkip.Checked;
        }

        private void btnCamera_Click(object sender, EventArgs e)
        {
            OpenCamera();
        }

        private void btnVideo_Click(object sender, EventArgs e)
        {
            OpenFileDialog dialog = new OpenFileDialog();
            DialogResult result = dialog.ShowDialog();
            if(result.Equals(DialogResult.OK))
                if(dialog.FileName != null)
                    OpenVideo(dialog.FileName);
        }
    }
}

fire detection using sensors

C/C++
fire detection using sensors for humidity, smoke, temperature, if any of those values are over a critical range, it will activate the dispersion of the extinguishing powder
#include <RF24Network.h>
#include <RF24.h>

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



#include <SPI.h>			//Bibloteka za SPI protokol koje NRF24 korist
#include <EEPROM.h>			//Biblioteka za EEPROM funkcije mikrokontrolera 
#include <DHT.h>            //Biblioteka za DHT senzor temperature i vlage

#define ONE_WIRE_BUS_1 2
#define DHTPIN 4
#define DHTVERZIJA DHT11	
#define CEPIN 9 
#define CSNPIN 10 
#define PROGRAMINGPIN 8 
#define RELAY 3

OneWire oneWire_in(ONE_WIRE_BUS_1);

DallasTemperature sensor_inhouse(&oneWire_in);

#define SERIALSPEED 9600



RF24 radio (CEPIN,CSNPIN);			//Kreiramo objekat radio koji prima parametre (CE pin , CSN pin)
RF24Network mreza(radio);			//Kreiramo objekat mreza kojem prosljedujemo objekat radio
DHT senzor(DHTPIN , DHTVERZIJA);	//Kreiramo objekat senzor koji prima parametre (DHTPIN  ,  DHTVERZIJA)

int adresa_trenutnog;						//Adresa ovog rutera ili end uredjaja
const uint16_t  adresa_kordinatora = 00;	//Adresa kordinatora (ESP8266)
byte sleep_interval;						//Vrijeme izmedju transmisija                
unsigned long tInterval;				   //Pomocna varijabla za tajmer
unsigned long tStart;                      //Pomocna varijabla za tajmer
bool timer = true ;						   //Bool kojim kontrolisemo interval slanja podataka	



 /* Varijable u koje spremamo sve podatke koje citamo sa senzora te ih prosljedujemo u strukturu paket i saljemo */
 
float temperatura;
float vlaga;
float indeks_topline;	


 
 
 /* Struktura koja cuva paket podataka koje saljemo kordinatoru (Node 00)  */
 
 struct paket_t
 {
	 float temperatura;			//Temperatura sa DHT11 senzora
	 float vlaga;				//Vlaga sa DHT11 senzora
	 float indeks_topline;		//Indeks topline sa DHT11 senzora
	 
 };

 

 /* Ova funkcija podesava sleep interval , izbor intervala je : 0 = 1 sec, 1 = 2 sec , 2 = 3 sec , 3 = 5 sec */
 
void podesiInterval(byte interval) 
{

	//postavljamo interval na 1 sekundu
	if(interval == 0)
	 { 
		tInterval = 1000; 
	}
	//postavljamo interval na 2 sekunde
	else if(interval == 1) 
	{ 
		tInterval = 2000;
	}
	//postavljamo interval na 3 sekunde
	else if(interval == 2)
	 {
		tInterval = 3000;
	}
	//postavljamo interval na 3 sekunde
	else 
	{
		tInterval = 5000;
	}
}



 /*	 Funkcija vraca true kada je vrijeme za slanje na osnovu postavljeni vrijednosti u EEPROM-u , a false u protivnom
     slucaju i za to vrijme dok je tajmer false mi prosljedujemo podatke od drugih nodeova prema  kordinatoru.        */

 
bool provjeriTajmer(void)
{
	unsigned long sada = millis(); // Zapocinjemo tajmer i spremamo vrjeme u varijablu sada
	if ( sada - tStart >= tInterval  ) //check to see if it is time to transmit based on set interval
	{
	 tStart = sada; // Resetujemo trenutno vrijednost tajmer na sadasnju
	 return true;
	}
	else 
	return false;
 }
 
 /*	 Funkcija koristi pointere , zahtjeva adrese koje prosljeudujemo koristeci operator '&' , a za izmjenu vrijednosti na adresi koristimo operator '*'      */
 
 void citajSenzore(float * temperatura , float * vlaga , float * indeks_topline  )
 {
	   *temperatura = senzor.readTemperature();
	   *vlaga = senzor.readHumidity();
	   *indeks_topline = senzor.computeHeatIndex(*temperatura , *vlaga , false); 
 }
 
 
		/* Ova funkcija nam omugucava programiranje nodeova putem serijske veze sa pc-om.Kada programiramo nesmjemo koristiti "Carige return " ili "Line Ending".
   U ovaj mod se ulazi tako sto se prvi put prikljuci i isprogramira node ili ukoliko pin za programiranje spojimo sa uzemljenjem.   */

void ucitajPostavke() 
{
	
	pinMode(PROGRAMINGPIN , INPUT); //Postavljamo pin za programiranje na stanje logicke jedinice , te ga postavljamo kao ulaz
	int adresa_nodea; //varijabla za spremanje adresse nodea
	byte adresa_praznog = 128; // Varijabla pomocu koje provjeravamo dali je cip bio prije programiran
	byte interval; //Vrijeme izmedju prenosa podataka
	
	//Ove stringove koristimo kako bi ustedjeli na memoriji 
	String potvrda = F("Unesi 'Y' za potvrdu ili bilo koji drugi karakter za odbijanje zahtjeva :");
	String greska  = F("Neispravan unost , postavljam na : ");
	String pitanje = F("Dali bi ste zeljeli promjeniti ");

	//Modul programiramo samoo ukoliko nije prije bio programiran ili je korisnik usao u mod za programiranje
	if(EEPROM.get(0,adresa_praznog)!= 128 || !digitalRead(PROGRAMINGPIN))
	 {
		Serial.begin(SERIALSPEED); //Zapocinjemo serijsku komunikaciju
		//Ovaj dio cita postavke trenutno spremljene u EEPROM , te ih prebacuje u lokalne varijable
		Serial.println(F("Trenutne postavke u EEPROM-u : "));
		Serial.print("Adresa node-a : ");
		Serial.println(EEPROM.get(1,adresa_nodea),OCT); 
		Serial.print("Sleep interval : ");
		Serial.println(EEPROM.get(3,interval));
		
		//Postavljanje Adrese node-a
		
		Serial.print(pitanje);
		Serial.print("adresu node-a ? ");
		Serial.println(potvrda);
			if (dajOdgovor())
			{
			Serial.println(F("Unesite novu adresu node-a :"));
			while (!Serial.available()) { }
			adresa_nodea = Serial.parseInt();
			if(adresa_nodea >= 0) {
				EEPROM.put(1, adresa_nodea);
			}
			else 
			{ 
				Serial.print(greska);
				Serial.println("01");
				adresa_nodea = 01;
				EEPROM.put(1, adresa_nodea);
			}
			}
			
		//Postavljanje vremenskog intervala node-a :
		
		Serial.print(pitanje);
		Serial.print("vremenski interval? ");
		Serial.println(potvrda);
		if(dajOdgovor()) 
		{
			Serial.println(F("Unesite : 0 za 1 sec, 1 za 2 sec, 2 za 3 sec , 3 za 5 sec"));
			while (!Serial.available()) { }
			interval = Serial.parseInt();
			if(interval >= 0 || interval < 4)
			 { 
				EEPROM.put(3, interval);
			 }
			else 
			{
				Serial.print(greska);
				Serial.println("3");
				interval = 3;
				EEPROM.put(3, interval);
			}
		}
		
		
		dajEEPROMvrjednosti(); //Sprema vrjednosti iz EEPROM-a u globalne varijable
		//Printamo vrjednosti spremljene u globalne varijable 
		Serial.print("Adresa nodea : ");
		Serial.println(adresa_trenutnog,OCT);
		Serial.print("Vremenski interval: ");
		Serial.println(sleep_interval);
		adresa_praznog = 128; //Pisemo '128' , kako nebi vise citao EEPROM kao prazan
		EEPROM.put(0, adresa_praznog);
		Serial.end(); //Prekid Serijske komunikacije
	}
	else 
	{
		//Ako nismo u modu za programiranje vadimo vrjednosti iz EEPROM-a i vracamo ih u globalne varijable
		dajEEPROMvrjednosti();
	}
}

/* Ova funkcija vraca podatke iz EEPROMA u globalne varijable na flash memoriji mikrokontrolera */

void dajEEPROMvrjednosti() 
	{
	EEPROM.get(1,adresa_trenutnog);
	EEPROM.get(3,sleep_interval);
	}

/* Ako korisnik pritisne 'Y' vraca true , za bilo koji slucaj je false */

bool dajOdgovor() 
{
	while (!Serial.available()) { }
	if(Serial.read() == 'Y')
	return true;
	else return false;
}

/* Ova funkcija vadi sleep interval i ispisuje ga na monitor */

String dajInterval() 
{
	byte i;
	EEPROM.get(3,i);
	if(i==0) 
	{
		return "1 sec";
	}
	else if (i==1) 
	{
		return ("2 sec");
	}
	else if (i==2) 
	{
		return ("3 sec");
	}
	else 
	{
		EEPROM.put(3,i);
		return ("5 sec");
	}
}

uint32_t detonated = 0;

void Detonate (void)
{
	detonated = millis();
	digitalWrite(RELAY , HIGH);
}

	
	
void setup(void)
{
	sensor_inhouse.begin();
	pinMode(RELAY,OUTPUT);
	
	digitalWrite(RELAY,LOW);
	ucitajPostavke(); //Ulazimo u mod za programiranje
	podesiInterval(sleep_interval);//Podesavamo vrijeme izmedju transmisija
	SPI.begin();	//Pocetak SPI komunikacije 
	radio.begin();	//Pocetak NRF24 komunikacije
	mreza.begin(90, adresa_trenutnog);  // Incijalizacija Mreze (prvi argument jeste kanal na kojem komuniciramo , drugi je adresa ovog modula)		
}

void loop(void)
{
	mreza.update();  // Provjerava dali ima podataka od drugih nodova za prosjljediti ka kordinatoru
	
	sensor_inhouse.requestTemperatures();
	float temp = sensor_inhouse.getTempCByIndex(0);
	Serial.println(temp);
	if(temp > 60)
		Detonate();
	if (timer) // Ako je vrijeme za slanje timer boolean je true
	{
		citajSenzore(&temperatura,&vlaga,&indeks_topline);
		paket_t paket = { temperatura , vlaga , indeks_topline  }; // Kreiramo paket za slanje
		RF24NetworkHeader header (adresa_kordinatora); //Pravimo Header koji ide u transmit paket kako bi nodovi znali na koju adresu trebaju prosljediti paket
		mreza.write(header,&paket,sizeof(paket)); //Slanje paketa
	}
	timer = provjeriTajmer(); // Osvjezavanja stanja tajmera pozivanjem funkcije provjeriTajmer koja vraca T ili F
	
}

communication between nodes

C/C++
wireless communication between different sensors, micro-controllers and the GPS sensor
#include <RF24.h>			//Bilbioteka za NRF24
#include <SPI.h>			//Bibloteka za SPI protokol koje NRF24 koristi
#include <RF24Network.h>	//Biblioteka za kreiranje  Mesh mreza sa NRF24

#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
#include <ESP8266HTTPClient.h>

#define CEPIN 2
#define CSNPIN 15


 struct paket_t
 {
   float temperatura;     //Temperatura sa DHT11 senzora
   float vlaga;       //Vlaga sa DHT11 senzora
   float indeks_topline;    //Indeks topline sa DHT11 senzora
   
 };

RF24 radio (CEPIN,CSNPIN);			//Kreiramo objekat radio koji prima parametre (CE pin , CSN pin)
RF24Network mreza(radio);			//Kreiramo objekat mreza kojem prosljedujemo objekat radio

ESP8266WiFiMulti WiFiMulti;

const uint16_t  adresa_kordinatora = 00;	//Adresa kordinatora (ESP8266)

void setup()
{
    Serial.begin(9600);
    Serial.println("Incijalizacija uspjesna ! ");
    SPI.begin();	//Pocetak SPI komunikacije
    radio.begin();	//Pocetak NRF24 komunikacije
    mreza.begin(90, adresa_kordinatora);  // Incijalizacija Mreze (prvi argument jeste kanal na kojem komuniciramo , drugi je adresa ovog modula)
    WiFiMulti.addAP("KPM 1", "plamenmira");

    while(WiFiMulti.run() != WL_CONNECTED) {
        Serial.print(".");
        delay(500);
    }
}



void sendUpdate(int node, paket_t packet)
{
	char getRequest[255] = "http://192.168.2.190/hak/?";
	char* last = getRequest + strlen(getRequest);
	
	strcpy(last, "temperatura=");
	last = getRequest + strlen(getRequest);
	dtostrf(packet.temperatura, 255 - strlen(getRequest), 2, last);
	
	last = getRequest + strlen(getRequest);
	strcpy(last, "&vlaga=");
	last = getRequest + strlen(getRequest);
	dtostrf(packet.vlaga, 255 - strlen(getRequest), 2, last);
	
	last = getRequest + strlen(getRequest);
	strcpy(last, "&indeks_topline=");
	last = getRequest + strlen(getRequest);
	dtostrf(packet.indeks_topline, 255 - strlen(getRequest), 2, last);
	
	last = getRequest + strlen(getRequest);
	strcpy(last, "&node=");
	last = getRequest + strlen(getRequest);
	sprintf(last, "%d", node);
	
	HTTPClient http;
	http.begin(getRequest); //HTTP
	Serial.println(getRequest);
	int httpCode = http.GET();

	// httpCode will be negative on error
	if(httpCode > 0) {
		// HTTP header has been send and Server response header has been handled
		Serial.print("[HTTP] GET... code: ");
		Serial.println(httpCode);
	}

	http.end();
}



void loop()
{
	mreza.update();  // Provjerava dali ima podataka od drugih nodova 
	paket_t paket; // Kreiramo objekat paket
	RF24NetworkHeader header; //Pravimo Header varijablu1

	if (mreza.available())
	{
		mreza.read(header,&paket,sizeof(paket));
		sendUpdate(header.from_node, paket);
	}
 }

Credits

Ajdin Nakičević

Ajdin Nakičević

1 project • 11 followers
- Electrical engineer - I like to build stuff

Comments