Paranoid fire/gas/motion alarm system based on a ESP8266

michalrz

Supreme [H]ardness
Joined
Jun 4, 2012
Messages
4,332
* Work in progress, see post 2 *

Hello everyone,

I've been slowly making a thing for a while now. As I have some spare time, some money, etc.

Basically what I'm building is a cheap but quite robust wireless environment sensing and alarming device.

Operating principle:
1. Detect a fire by sampling multiple sensors. Try to establish the likelihood of false alarm/sensor malfunction/actual fire.
- is it getting more hot than usual?
- optical detection of an open flame via infrared?
- is there smoke?
- smoke detection by checking both the MQ-2 sensor's reading AND a simple diode emitter-phototransistor setup
- (added 26.04) (removed 12.06, see below) measure supply voltage, notify in event of insufficient power

2. Detect presence of combustible gases (only tested smoke and Buthane for now, using MQ-2)
2a. (added 26.04) MQ-2 is a tube-like sensor. It needs to warm up for a few minutes and have adequate current available.
During startup, current draw to MQ-2 will be measured to determine sensor readiness and a baseline readout will be established (calibration).
(added 01.05) - measuring the MQ-2 sensor's current draw isn't helpful for determining readiness because power supply fluctuations and humidity affect the readout more than the heating up... But a baseline will be recorded.

3. Motion detection (optional)
- motion detection via passive infrared (PIR) sensors
- multiple sensors, partially overlapping to help identify false alarms


4. Shit's on - tell b0ss
- the ESP8266 by default supports WiFi.
- on alarm, send e-mail, detail which sensors tripped
- (21st May - removed - complex, expensive, harder to obtain) send SMS message (there are GSM/GPRS modules available for Arduino, ESP8266, etc.)
- (21st May - removed - complex, expensive, harder to obtain) send SMS message via an API / SMS gateway on the interwebs
- buzzer buzzes. Will adjust signal based on how real the shit has gotten (multiple sensors tripped?)

4a. Level of paranoia (panic thresholds) can be configured.

5. And here's the kicker.
- total cost of device - around $30-$40.
- it's so cheap because all the mentioned parts are very inexpensive and can be had for peanuts.
They aren't obviously pr0 grade calibrated certified etc, but using multiple methods of sensing the environment makes up for that.
- because it's cheap, multiple devices can be used and there's no need for a compromise (which room? which corridor?)

Current parts list:
- a Wemos D1 development board, based on the ESP8266 microcontroller ($5)
- OLED 0,91" display ($5)
- MQ-2 LPG, Smoke, Alcohol, Propane, Hydrogen, Methane and Carbon Monoxide sensor ($2)
- optical fire sensing detector (760 to 1100 nm) ($3)
- AM2302 based temperature and humidity sensor ($5?)
- (removed 21st May - complex, expensive, harder to obtain) GSM module - $10 (?)
- backup power in the form of any small power bank ($8?)
- wall adapter to charge/top off the power bank
- wires and a plastic box ($5?)
- (removed 12.06-cost, can be done with an analog2digital channel, ESP8266 has just one, ESP32 has more for a similar price) INA-219 digital current & voltage sensor module ($4)
- (added 13.05) Keyes KY-006 passive buzzer (volume adjustable in software via PWM)
 

Attachments

  • WP_20210420_19_23_57_Pro.jpg
    WP_20210420_19_23_57_Pro.jpg
    369.3 KB · Views: 0
Last edited:
Because this is a work in progress, I'll be adding some some stuff in this thread as time goes on.

Code will be published here.

Everyone's welcome to throw in their suggestions, but please be uhhh terse and succint :D
 
Last edited:
The choice of a Wemos D1 ESP8266 dev. board is not random.
You may have heard/read about the ESP32 or beefier ESP8266 versions, but...

- Earlier revisions of this device can be bought for crazy low clearance prices. I got mine for 20 PLN ( around $5 )
- You can supply power through a jack connector, the "VIN" pin, or through the USB connector.
- You can supply either 5V or 12V. The board has a built-in buck converter and exposes 3.3V and 5V for use by various modules via pins.
 

Attachments

  • WeMos-D1-R1-WiFi-Development-Board-2.jpg
    WeMos-D1-R1-WiFi-Development-Board-2.jpg
    204.6 KB · Views: 0
Update: added INA219 current/voltage sensor to parts list for important additional features
 
I'm considering dropping the IR diode - phototransistor mode of smoke detection, and motion sensor in favor of adding a GSM module to send texts when an alarm is raised.
Reason for this decision would be component count, which adds to complexity, which usually reduces stability of any device.
I'll think about it.

However, the power consumption numbers are looking good for a basic smoke/gas/temperature setup I have going now.
Todo: enable WiFi, reports.
power consumption.jpg
 
1st of May
- decided to drop the motion detection portion - no point in trying to have it do everything. Complexity/parts count/power.
- decided to drop IR-diode/photo-transistor smoke detection: a ready-made sensor is surprisingly costly and also real estate issues. Might re-think after everything else is done.
- ordered GSM module, small passive buzzer

Good news - power supply voltage measurement works, device reboots/freezes/glitches below 4.6V, but will alert on flaky power.
The built-in hardware watchdog reboots the ESP8266 controller, so as long as there's power, it will start up and work unattended.

Weird bug: don't know if that's an issue with just my power bank... When the power bank discharges to 0, and mains power is restored, the power bank's output is really bad during top-up, despite good mains power. In my case, it went to 4.3V, which caused all sorts of problems (device worked, but badly).

So I guess the next update will be when I get GSM and WiFi working.
 

Attachments

  • zestaw-z-modulem-gprs-gsm-sim800l-v20.jpg
    zestaw-z-modulem-gprs-gsm-sim800l-v20.jpg
    33.2 KB · Views: 0
Still waiting on that GSM module...
E-mail notification is almost done, though. I don't like the idea of this, as e-mail is definitely not 100% reliable.
The board I picked has some irritating design issues. Some outputs/inputs are double purpose and routed weird.

Here's an al-quaeda quality video of the device starting up, working normally (cycling through various info like temperature, humidity...) and then responding to an optical flame detection by going into warning mode (a countdown starts, buzzer beeps, etc).
When another type of warning gets triggered while one is already active (temperature rising, smoke) the device goes into 'alarm' mode (SMS, louder buzzer...).



In the code, pre-compilation, the user can choose (among others): how many light flashes is permissible, the level of the gas sensor's sensitivity, how long should each type of warning last, buzzer volume...
 
Well, I assembled the thing.
I picked a much too small of an enclosure, so despite my best efforts, it does resemble an IED and not an appliance.

I'll produce a wiring diagram in a few days.

Code will be dropped later, I have to translate it.
 

Attachments

  • WP_20210516_18_51_02_Pro.jpg
    WP_20210516_18_51_02_Pro.jpg
    341 KB · Views: 0
  • WP_20210516_18_54_05_Pro.jpg
    WP_20210516_18_54_05_Pro.jpg
    259.7 KB · Views: 0
This is a suggestion on how to wire things.

Notes:
- You can power the device via the microUSB port, the jack, or by connecting a 5-12V source to the VIN and GND pins. Current consumption will work properly only with the 3rd method, but voltage sensing will work in either.
- Mind the LEDs rated voltage and current (apply a 200-1000Ohm resistor where applicable and to tweak brightness according to your needs)
- The potentiometer on the MQ-2 sensor determines which _analog_ value will be considered the threshold for outputting digital HIGH. We're not using the digital output, relying on the Analog signal instead. Allows more customization in software according to one's needs. Place a small resistor between sensor's analog out and the board's A0 analog input, 1k-3k will be fine. You need this resistor to protect the sensitive analog-to-digital converter of the ESP8266.
- The potentiometer on the optical flame sensor tweaks sensitivity - turn all the way right until it gets 'stuck' at HIGH and then give it a little nudge to the left. This will yield good sensitivity with less risk of false positives.
- Be careful while wiring the INA219 voltage/current sensor. Wire it as if it were an ammeter (in series) and not a voltage meter (parallel). Grounding the 'Vin-' pin while power is applied to the 'Vin+' pin will cause a short and you'll need a fire alarm for your fire alarm... (can you guess how I found that out?)
- You will have to 'break out' the 5V, GND, SCL (i2c bus clock) and SDA (i2c bus data) outputs in order to connect everything. With the i2c SCL and SDA pins, simply tapping to the second pair or SDA/SCL pins doesn't work for some reason. Choose one, break it out.
I'm attaching an example of a breakout board of sorts that can be made with some goldpin connectors. You can also use a tiny proto-board.

Flame and gas/smoke sensors (certainly not cheap DIY like these) are not suitable for a large space. The sensitivity just isn't there.
Their operation will depend on the users' individual environment - humidity, temperature, ventilation - all affect sensitivity.
This is why we're using 3 methods instead of one. Also, what we have on our side are numbers, as all required parts are super cheap.

You can find many implementations of every sensor I'm using here. They should all work. They may differ only in pinout, so do mind the pin assignments.
 

Attachments

  • breakout.jpg
    breakout.jpg
    81.2 KB · Views: 0
  • esp8266 fire-2.png
    esp8266 fire-2.png
    1.5 MB · Views: 0
Last edited:
Here's the working code for the device.
For the IDE one should use the Arduino IDE w/ the following libraries:
- DHT
- SSD1306 Oled driver & GFX
- Wire (i2c bus)

That's it for now, but I'll add the e-mail code later on also.

Code:
/* VERSION 1.0 */

#include "DHT.h"
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels
#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

#define DHTTYPE DHT22 //temp&humidity sensor

/*The flame sensor's output is momentary.
How many momentary flame detections in 10 seconds
should be considered an actual flame and not a glitch
For full paranoia set 1, for a lax setup set >5 */
int THRESH_IS_FLAME = 5;

/* MQ-2 can give a non-zero value even as a baseline (no detection)
therefore, the below value is added to the baseline and a threshold
is thus defined. Note: Reading can be 0-1024, so 100 is an okay value
increase if you get false positives during changes of temperature or humidity*/
int THRESH_LEVEL_SMOKEGAS=100;

/* MQ-2's output goes back to normal rather quick after the cause is gone.
How many MQ-2 above-threshold readings during 10 seconds
should be considered cause for alarm.
For paranoia leave at 1. For a lax setup, in a drafty space, increase. */
int THRESH_IF_SMOKEGAS=1;

/* What difference of temperature should be considered abnormal (Celsius)
Increase for small rooms, decrease for big ones (for sensitivity) */
float LARGE_JUMP_TEMP=2;

/* How many of the above defined temperature jumps during 10 minutes
should be considered cause for alarm. Very dependant on your environment. */
int ACCEPTABLE_TEMP_JUMPS=3;

/* Range for volume is 1-1023. Default is quiet for testing purposes. */
int BUZZER_VOLUME=10;  // 1 - 1023 being the loudest

//////DURATION OF WARNINGS
int DURATION_FLAME_WARNING=60;
int DURATION_SMOKE_WARNING=60;
int DURATION_TEMPJUMP_WARNING=180;

////TIMERS FOR WARNINGS
int timerFlameWarning=0;
int timerSmokegasWarning=0;
int timerTempjumpWarning=0;

//TIMEKEEPING
unsigned int timer_seconds=0;
unsigned int timer_1s=0;
unsigned int timer_3s=0;
unsigned int timer_10s=0;
unsigned int timer_60s=0;
unsigned int uptime_minutes=0;
//FOR THE HEARTBEAT LED
bool tiktak=false;

//PINS
uint8_t DHTPin = D13; //temp&humidity sensor
int pin_gas_analog = A0;    // used for ESP8266
int pin_flame_sensor = D9;
//int pin_flame_sensor2 = D9; //you can add flame sensors
int pin_heartbeat_led=D11;
int pin_warn_led=D12;
int pin_buzzer=D6;

DHT dht(DHTPin, DHTTYPE);

///SENSOR DATA
float humidity;
float temperature;
float temperatures_every_10s[6];
float temperatures_every_60s[10];
float smokeGasReadings_every_1s[10];
int flame_sensor_readings[10];
int gas_smoke_analog;
int flame = 0;
/* Polled values are kept in static arrays because I suck at pointers. */
char cursor_temperatures_every_10s=0;
char cursor_flame_sensor_readings=0;
char cursor_temperatures_every_60s=0;
char cursor_smokeGasReadings_every_1s=0;

/* This special variable is set during a routine called
during an interrupt, which is a high-priority event handler
when a flame detection is... detected. */
volatile int flame_detected_during_last_second=0;

//Average baseline set during calibration
int average_baseline_smoke_reading=0;

////FLAGS
bool flameWarning=false;
bool smokeWarning=false;
bool tempjumpWarning=false;
bool masterAlarm=false;

const char *display_modes[5] = {"temp", "humidity", "uptime", "", ""};
//last 2 empty are placeholders for warn and alarm
char current_display_mode=0;

/* ESP8266 calls this out of order when the flame sensor gets triggered
Avoid putting handling code here. Set a flag and handle somewhere in the main loop.

CAUTION: Do NOT use delay() anywhere in the loop or interrupt routines.
CAUTION: Delay() can have a detrimental effect on stability. You can do it in setup().

IRAM_ATTR is a directive which places the function in RAM for faster calling. */
void IRAM_ATTR detected_flame(){
  flame_detected_during_last_second=1;
}

/* OLED display screens */
void display_mode_humidity(){
  display.clearDisplay();
  display.setCursor(0,0);
  display.print(humidity);
  display.print(" %");
  display.display();
}

void display_mode_temperature(){
  display.clearDisplay();  
  display.setCursor(0,0);             // Start at top-left corner            
  display.print(temperature);
  display.print(" C");
  display.display();
}

void display_mode_uptime(){
  int hours=0;
  int minutes=0;
  hours=floor(uptime_minutes/60);
  minutes=uptime_minutes%60;
  display.clearDisplay();  
  display.setCursor(0,0);             // Start at top-left corner
  display.print(hours);
  display.print("g ");
  display.print(minutes);
  display.print("m");
  display.display();
}

void display_mode_warning(){
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);        // Draw white text
  display.setCursor(0,0);             // Start at top-left corner
  display.setTextSize(1);             // Draw 2X-scale text
  display.print("Temp     Flame    Gas\n");
  display.setTextSize(2);
  display.print(timerTempjumpWarning);
  display.print("   ");
  display.print(timerFlameWarning);
  display.print("  ");
  display.print(timerSmokegasWarning);
  display.display();
}

void display_mode_alarm(){
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);        // Draw white text
  display.setCursor(0,0);             // Start at top-left corner
  display.setTextSize(2);             // Draw 2X-scale text
  display.print("ALARM");
  display.display();
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  Serial.setDebugOutput(true);
 
  pinMode(DHTPin, INPUT);
  dht.begin();

  pinMode(pin_flame_sensor,INPUT);
  /* If you add more flame sensors, set their mode here.  */
  // pinMode(pin_flame_sensor2,INPUT);
  pinMode(pin_heartbeat_led, OUTPUT);
  pinMode(pin_warn_led, OUTPUT);
  pinMode(pin_buzzer, OUTPUT);

  /* If the display can't be started, light the LEDS up.
  Purpose: so you'll know it's the OLED that failed during boot-up. */
  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    digitalWrite(pin_heartbeat_led, HIGH);
    digitalWrite(pin_warn_led, HIGH);  
  }
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);        // Draw white text
  display.setCursor(0,0);             // Start at top-left corner
  display.setTextSize(1);             // Draw 2X-scale text
  attachInterrupt(pin_flame_sensor, detected_flame, FALLING);
  /* This is where we bind interrupt routines to sensor pins
  The below one is how you'd add your flame sensors. */
  //attachInterrupt(pin_flame_sensor2, detected_flame, FALLING);  
  /* Calibration is a fancy word for these things:
  1. What is the baseline output value of the MQ-2 sensor during normal conditions.
  2. Does the flame sensor get randomly triggered, which can suggest bad power or bad wiring.
  3. The ALARM LED can be checked (should fade in)
  4. The buzzer should buzz (should fade in)
  */
  int time_calibration_start=millis();
  int time_calibration_end=millis()+10000;
  int calibrationLoopCounter=0;
  float calibratingProgress=0;
  while(millis()<time_calibration_end){
      display.clearDisplay();  
      display.setCursor(0,0);  // Start at top-left corner    
      display.print("Calibrating\n");
      calibratingProgress=(float)millis()/time_calibration_end*100;
      display.print(calibratingProgress);
      display.print(" %\n");
      display.display();    
      average_baseline_smoke_reading+=analogRead(pin_gas_analog);
      analogWrite(pin_buzzer, (int)calibratingProgress);
      delay(100);
      calibrationLoopCounter++;    
  }
  analogWrite(pin_buzzer, 0);  
  average_baseline_smoke_reading=average_baseline_smoke_reading/calibrationLoopCounter;
  display.clearDisplay();  
  display.setCursor(0,0);
  if(flame_detected_during_last_second==1){  
    display.print("WARNING\nspurious optical\nflame detection\n");
  } else display.print("OK No spurious\n optical flame\n detections");
  display.display();
  delay(4000);
  THRESH_LEVEL_SMOKEGAS=average_baseline_smoke_reading+THRESH_LEVEL_SMOKEGAS;  
  display.setTextSize(2);
}

void display_mode_humidity();
void display_mode_temperature();
void display_mode_uptime();
void display_mode_warning();
void display_mode_alarm();

void display_oled(){
  if(display_modes[current_display_mode]=="temp"){
      display_mode_temperature();
  }
  if(display_modes[current_display_mode]=="humidity"){
      display_mode_humidity();
  }
  if(display_modes[current_display_mode]=="uptime"){
      display_mode_uptime();
  }
  if(display_modes[current_display_mode]=="warn"){
      display_mode_warning();
  }
  if(display_modes[current_display_mode]=="alarm"){
      display_mode_alarm();
  }
  current_display_mode++;
  if(current_display_mode>=sizeof(display_modes)/sizeof(display_modes[0]))current_display_mode=0;
}

void heartbeat(){
  if(tiktak==false){
      digitalWrite(pin_heartbeat_led,HIGH);    
      tiktak=true;
    }else{
      digitalWrite(pin_heartbeat_led,LOW);
      tiktak=false;
    }  
}

void warning_notify(){
  if(flameWarning||smokeWarning||tempjumpWarning){
    if(tiktak==false){
      digitalWrite(pin_warn_led,HIGH);
      analogWrite(pin_buzzer, BUZZER_VOLUME);
    }else{
      digitalWrite(pin_warn_led,LOW);
      analogWrite(pin_buzzer, 0);
    }
    display_modes[3]="warn";  
  }else {
    digitalWrite(pin_warn_led,LOW);
    analogWrite(pin_buzzer, 0);
    display_modes[3]="";
  }
}

//warning handler
void warningHandler(){
 
}

void alarm_notify(){
  if(flameWarning&&smokeWarning){
    //smoke/gas and open flame
    masterAlarm=true;
    handleAlarm();
  } else if(tempjumpWarning&&smokeWarning){
    //smoke/gas and rising temperature
    masterAlarm=true;
    handleAlarm();
  } else if(flameWarning&&tempjumpWarning){
    //flame and rising temperature
    masterAlarm=true;
    handleAlarm();
  } else {
    masterAlarm=false;
    display_modes[4]="";
  }
}

//alarm handler
void handleAlarm(){
  display_modes[4]="alarm";
  analogWrite(pin_buzzer, BUZZER_VOLUME);
}

float calculateAvgTempMinute(){
    float temperature_average_60s=0;
    for(int i=0;i<6;i++){
      temperature_average_60s+=temperatures_every_10s[i];    
    }
    temperature_average_60s=temperature_average_60s/6;
    return temperature_average_60s;
}

void decisionAlarmFlame(){
  int detections_in_10s=0;
  for(int i=0;i<10;i++){
      detections_in_10s+=flame_sensor_readings[i];
      Serial.print(   flame_sensor_readings[i]);
      Serial.print(" ");      
  }
  Serial.print(" \n");
  if(detections_in_10s>=THRESH_IS_FLAME){
    flameWarning=true;
    if(timerFlameWarning==0)timerFlameWarning=DURATION_FLAME_WARNING;  
  }
  if(timerFlameWarning>0)timerFlameWarning--;
  if(timerFlameWarning==0)flameWarning=false;
}

void decisionAlarmSmoke(){
  int detections_in_10s=0;
  for(int i=0;i<10;i++){
      if(smokeGasReadings_every_1s[i]>THRESH_LEVEL_SMOKEGAS){
        detections_in_10s++;
      }
  }
  if(detections_in_10s>=THRESH_IF_SMOKEGAS){  
    smokeWarning=true;
    if(timerSmokegasWarning==0)timerSmokegasWarning=DURATION_SMOKE_WARNING;  
  }
  if(timerSmokegasWarning>0)timerSmokegasWarning--;
  if(timerSmokegasWarning==0)smokeWarning=false;
}

void decisionAlarmTempjump(){
  //min i max tez
  int jumps=0;
  for(int i=1;i<10;i++){  
    if(temperatures_every_60s[i]-temperatures_every_60s[i-1]>=LARGE_JUMP_TEMP){
      jumps++;
    }
    Serial.print(temperatures_every_60s[i]);
    Serial.print(" ");
  }
  Serial.print("temp jumps ");
  Serial.print(jumps);
  Serial.print("\n");

  if(jumps>ACCEPTABLE_TEMP_JUMPS){
    tempjumpWarning=true;
    if(timerTempjumpWarning==0)timerTempjumpWarning=DURATION_TEMPJUMP_WARNING;  
  }
  if(timerTempjumpWarning>0)timerTempjumpWarning--;
  if(timerTempjumpWarning==0)tempjumpWarning=false;
}

void loop() {
  // put your main code here, to run repeatedly:
  if(timer_seconds==0){
    timer_seconds=millis()+1000;
  }
  if(millis() >= timer_seconds){
    timer_1s++;
    timer_3s++;
    timer_10s++;
    timer_60s++;
    timer_seconds=0;  
  }
 
  if(timer_1s >= 1){
    timer_1s=0;
    heartbeat();  
    /////SENSOR POLLING
    humidity = dht.readHumidity();
    temperature = dht.readTemperature();
    gas_smoke_analog=analogRead(pin_gas_analog);  
    ////  
    smokeGasReadings_every_1s[cursor_smokeGasReadings_every_1s]=gas_smoke_analog;  
    if(flame_detected_during_last_second>0){
      flame_sensor_readings[cursor_flame_sensor_readings]=1;
      flame_detected_during_last_second=0;
    }
    else flame_sensor_readings[cursor_flame_sensor_readings]=0;
    cursor_flame_sensor_readings++;
    cursor_smokeGasReadings_every_1s++;
    if(cursor_flame_sensor_readings>9)cursor_flame_sensor_readings=0;
    if(cursor_smokeGasReadings_every_1s>9)cursor_smokeGasReadings_every_1s=0;
    decisionAlarmFlame();
    decisionAlarmSmoke();
    decisionAlarmTempjump();
    warning_notify();
    alarm_notify();    
    ///////TIMERS  
    Serial.print("Flame warning timeout: ");
    Serial.print(timerFlameWarning);
    Serial.print("\n");
    Serial.print("Gas/smoke warning timeout: ");
    Serial.print(timerSmokegasWarning);
    Serial.print("\n");
    Serial.print("Temperature increase timeout: ");
    Serial.print(timerTempjumpWarning);
    Serial.print("\n");  
  }

  if(timer_3s >= 3){
    timer_3s=0;
    display_oled();  
  }

  if(timer_10s >= 10){
    timer_10s=0;  
    temperatures_every_10s[cursor_temperatures_every_10s]=temperature;
    cursor_temperatures_every_10s++;
    if(cursor_temperatures_every_10s>6)cursor_temperatures_every_10s=0;  
  }

  if(timer_60s >= 60){
    uptime_minutes++;
    timer_60s=0;
    temperatures_every_60s[cursor_temperatures_every_60s]=calculateAvgTempMinute();
    cursor_temperatures_every_60s++;
    if(cursor_temperatures_every_60s>9)cursor_temperatures_every_60s=0;  
  }
}
 
Back
Top