Friday, 29 June 2018

Huzzah Weather Display

Huzzah Weather Display

 

The Huzzah ESP8266 module is an inexpensive microcontroller with WiFi capabilities. It can be programmed with the Arduino IDE so that you don't need a separate microcontroller! This device gets the latest weather information from DarkSky.net and displays it on a 1.3" monochrome OLED display.
Note on difficulty: this is an intermediate level project and involves running a PHP script on a web server to parse data from DarkSky.net before it gets read by the Huzzah. It's helpful to know some basics about PHP and web servers before starting this project!
This project was created by Daniel Eichhorn and the code was forked to work with Adafruit hardware and libraries.
The weather icons were created by Alessio Atenzi.
Dano Wall designed the lasercut enclosure.

Parts List

Electronics Parts


Enclosure Parts

  • 2 x laser-cut acrylic pieces (download vectors from repository)
  • 3 x 1" 2-56 screws
  • 1 x 3/4" 2-56 screw
  • 2 x 1/4" 2-56 screw
  • 15 x 2-56 hex nuts
  • 2 x 3/16" nylon spacers 

 

Tools

Circuit Assembly

Overview

The goal is to solder the Huzzah and OLED breakout boards so that the OLED screen and Huzzah module are facing in opposite directions. The two buttons on the Huzzah need to be accessible to put the module into bootloader mode and program the Huzzah.
The power and ground will ultimate be connected to the 5V and GND pins on the microUSB breakout board. We'll use a microUSB cable to power the device.

First Steps

On the OLED breakout, solder the two solder jumper pads SJ1 and SJ2 so that you can use the I2C protocol. Do this before soldering the boards together!
You should get a 6-pin right angle header to solder into the Huzzah FTDI pins. Do this before soldering other things together! Be sure to solder all six headers securely.
Ultimately you will solder the two boards with a header pin lining up V+ on the Huzzah and Vin on the 1.3" OLED. This way all the boards will stack up nicely. Make the other wire connections before soldering the two together however, since it will make the process easier. I've found that thin-gauge, wrapping style wire works best.
Start by soldering a header pin into the Vin on the 1.3" OLED breakout.
If we examine the schematic again, notice that the Huzzah has two GND pins. Since there will be multiple connections to the same GND pin, it can be helpful to solder a male header pin into one of the GND pins. Make sure it's sticking out more on top, so solder it on the bottom of the Huzzah board.
Now make the other connections between the Huzzah and OLED breakout board following the schematic below using thin wire. Remember, ultimately you'll use the header pin soldered into the OLED breakout to make the 5V power connection between the two boards.
From the Huzzah to the OLED breakout, connect:
  • pin 2 to Rst
  • pin 12 to Clk
  • pin 14 to Data
  • GND to GND
Now you can solder the Huzzah and OLED breakout boards together. They should be facing away from each other and lined up like this. Place the Vin header pin on the OLED breakout into the V+ on the Huzzah.
Now connect the MicroUSB breakout. Connect the 5V pin to the header pin going through V+ on the Huzzah.
You can also connect the GND pin on the MicroUSB breakout to the header pin sticking out of the Huzzah GND.
Once you've attached the MicroUSB breakout you can cut down the excess header on the V+ pin and GND pin. The circuit is assembled!

PHP script

PHP script

Download the code from github by clicking Download ZIP. Uncompress the file and find the folder named php.
Using an FTP client, upload these files to a web server which runs PHP-- most of them do. You can even run it on a Raspberry Pi on your local network. Be sure to also include the contents of the lib folder.
This PHP script parses some information from DarkSky.net and takes some of the heavy string processing off the shoulders of the ESP8266.
The script is setup to get the temperature in Fahrenheit and other information in English. If you'd like to change this modify the following lines of code:
  1. $units = 'us'; // Can be set to 'us', 'si', 'ca', 'uk' or 'auto' (see forecast.io API); default is auto
  2. $lang = 'en'; // Can be set to 'en', 'de', 'pl', 'es', 'fr', 'it', 'tet' or 'x-pig-latin' (see forecast.io API); default is 'en'
Go to https://darksky.net/dev/ and sign up for a free API key. At the free level there's a pretty generous limit to the number of requests per day you can make, well below what we'll be using.
It's a good idea to test the PHP script at this point by pointing your web browser to: http://YOUR_DOMAIN_NAME/weather.php?apiKey=APIKEY&lat=40.71&lon=-74&units=us
Alter this URL to point to your PHP script, replacing YOUR_DOMAIN_NAME with the actual domain name hosting your script and replacing APIKEY with the API key you received from forecast.io. This longitude and latitude are for New York City- feel free to change them to get the weather for your location in the world.  NASA has a good tool for finding your longitude and latitude.
If you manage to enter the URL correctly, your web browser should give you a webpage that looks like this:

Uploading Code

Arduino code

Make sure to use Arduino IDE 1.6.4 or higher and follow this tutorial to install the ESP8266 board packages.
Download the code from github by clicking Download ZIP. Uncompress the file and copy the folder to your Arduino sketchbook folder.
Attach the Huzzah ESP8266 to your computer using an FTDI cable. Since it's possible to connect the FTDI cable backwards, make sure to line up the black wire on the FTDI cable to the GND pin on the 6-pin connector. The Huzzah will get power from the FTDI cable, you don't have to have the microUSB plugged in.
Once that is done, open the sketch and select:
Tools → Board → Adafruit HUZZAH ESP8266
CPU Frequency → 80 MHz
Upload Speed → 115200
Port → select the USB port with the FTDI cable
Make sure the code compiles properly. All of the relevant libraries should be included.
For more information on using the Huzzah ESP8266 module with the Arduino IDE, follow this tutorial.

Modifying the Code

You'll need to make some modifications to the code to make it work properly on your WiFi network.
  1. #define WIFISSID "YOUR_WIFI_SSID"
  2. #define PASSWORD "YOUR_WIFI_PASSWORD"
Replace YOUR_WIFI_SSID with the name of your WiFi network and PASSWORD with your WiFi password. Keep the quotation marks!
  1. #define FORECASTAPIKEY "YOUR_FORECAST_API_KEY"
Replace YOUR_FORECAST_API_KEY with the API key you received when you signed up with DarkSky.net. Keep the quotation marks!
  1. #define DOMAINNAME "YOUR_DOMAIN_NAME"
Replace YOUR_DOMAIN_NAME with the domain name hosting the PHP script, e.g. adafruit.com. Keep the quotation marks!
In WeatherClient.cpp feel free to alter the URL path to reflect the location of your PHP script on the web.
It's a good idea to test the PHP script at this point by pointing your web browser to: http://YOUR_DOMAIN_NAME/weather.php?apiKey=APIKEY&lat=LATITUDE&lon=LONGITUDE&units=UNITS making the appropriate substitutions for APIKEY, LATITUDE, LONGITUDE and UNITS.
  1. // New York City
  2. #define LATITUDE 40.69
  3. #define LONGITUDE -73.99
This longitude and latitude are for New York City- feel free to change them to get the weather for your location in the world.  NASA has a good tool for finding your longitude and latitude.

Uploading the Code

Attach the Huzzah to your computer using an FTDI cable, making sure to plug it in the right way. When you're ready to upload the code, put the Huzzah module into bootloader mode. You'll have to do this before each upload. There is no timeout for bootload mode, so you don't have to rush!
  1. Hold down the GPIO0 button, the red LED will be lit
  2. While holding down GPIO0, click the RESET button
  3. When you release the RESET button, the red LED will be lit dimly, this means its ready to bootload
This is what a successful upload will look like:
It can be difficult to access the buttons once the module is in the enclosure! The best way I've found is to manipulate the two buttons with two pairs of tweezers.

Debugging

Use the Arduino IDE Serial Monitor at 115200 baud to check for error messages. The device should connects to WiFi and download the webpage immediately, and then do this again every ten minutes.
If the Huzzah connects to the WiFi network and webpage correctly, the window should look something like this:
Make sure the URL matches the actual location of your PHP script. You can test it by pasting it into a web browser.
If the code is uploaded correctly, you should see the display showing the weather for "Now", "Today" and "Tomorrow".

Enclosure Assembly

The lasercut enclosure consists of two laser-cut pieces of acrylic held together by screws. The vector files are available from the Github repository or you can download them here:
The enclosure can be put together with some 2-56 machine screws and hex nuts:
  • 3 x 1" 2-56 screws
  • 1 x 3/4" 2-56 screw
  • 2 x 1/4" 2-56 screw
  • 15 x 2-56 hex nuts
  • 2 x 3/16" nylon spacers 
Start by removing the protective sticker from the OLED breakout. Place it into the laser-cut acrylic piece with the corresponding hole or window. Notice the small notch which corresponds to the OLED ribbon connector. It can help at this point to remove the double-stick tape affixing the OLED to the breakout PCB in order to get things lined up well.
Start by putting a 1" screw through the top left hole (if the screen is facing you). This screw will go through all three breakout boards, lining them all up! Start by putting two hex nuts between the the OLED breakout and the Huzzah breakout and putting the screw all the way through. Don't overtighten though!
Add a 3/16" nylon spacer and hex nut at the end.
Place the 3/4" screw into the top right hole of the Huzzah breakout. You might need to angle this in to get past the OLED breakout. Tighten down with a hex nut.
Place the 1/4" screw through the top right hole of the acrylic and secure it on the other side of the OLED breakout with a hex nut. This can be a bit tricky- you may have to hold the hex nut in place with needle-nose pliers while you tighten the screw with a screwdriver.
Add a nylon space and hex nut to the 3/4" screw.
Attach the microUSB breakout to the other piece of lasercut acrylic. Use a 1/4" screw and hex nut to fix the left hole of the breakout to the acrylic on the side with the cutout notch. The cutout notch is there to allow a microUSB cable to connect.
Place the other hole of the microUSB breakout through the 1" screw and secure with a hexnut. You can also secure the other side of the acrylic with a hex nut.
Now for the easy part! You just need to add the bottom two screw. The spacing can be set using three hex nuts here.
Add another 1" screw on the other side and do the same with 3 more hex nuts.
You're done with the enclosure!
Attach a microUSB cable to a computer or 5V power adapter and power it up!

Tuesday, 26 June 2018

Tech Owl

How to Build a Tech Owl That Will Make You Air Quality Wise #CitizenScience #Arduino #DIY #science #air

AirOwl Arduino Air Quality Monitor
This owl is not only an adorable DIY project, but a practical air quality monitor. Posted by Team Oizom (Anith Patel and Sohil Patel) on Hackster.io, AirOwl uses an Arduino 101, a dust sensor and Adafruit Neopixels to reveal air quality. Data is displayed in three ways—through a mobile Blynk app, the color of the owl’s eyes and within Arduino’s serial monitor. Of course the eyes are most fun, enabling you to get an instant read from a distance. Green=safe environment, Blue=take care and Red= danger.
AirOwl Screen Shot
The app is easy to put in place and uses BLE to bring data into gauges for the different particulate matter types. The team has placed instructions for setting up the app on their project page, as well as links to Github for the owl design files, PCB files and code. This project won 3rd place (student) in Hackster’s Earth Day: Planet Pulse contest. For any teacher incorporating STEM education, this would be a great project to build and monitor throughout the year, especially if your classroom has windows that open. Even if you don’t have the capability to produce the pro owl design, you could still work with cardboard and opaque paper to create a similar look, paired with the breadboard version of electronics. Sending big high fives to Team Oizom for creating a project that is well suited for classrooms and citizen scientists.

Air Quality Analyzer

Analyses home air quality and records the values in a SD card.
Air Quality Analyzer

Things used in this project

Hardware components

ADAFRUIT FEATHER M0 ADALOGGER

×1


Adafruit MONOCHROME 0.96" 128X64 OLED GRAPHIC DISPLAY

×1


ADAFRUIT CCS811 AIR QUALITY SENSOR BREAKOUT - VOC AND ECO2

×1


DHT11 Temperature & Humidity Sensor

×1


Jumper wires (generic)
around 20 male to male 15cm jumpers.
×2

Software apps and online services


Arduino IDE



Hand tools and fabrication machines


Soldering iron (generic)





Story


When the display works correctly you can proceed to the next section:

Connecting the Sensors:

Connect DHT 11 according to the diagram. (Don't use the pin D13 on the board to read DHT11 data. Pin D13 has a LED connected permanently, it presents a low impedance and is not suitable for this kind of use).
We have to use one of the free digital pins. We excluded D9 because it conflicts with A7 used to read Vbat, and D13 because of the LED circuit. I will use D19 to connect to DHT11 pin 2, and read temperature and humidity via its one wire protocol. D19 is marked as A5 on the board, but can also be used as Digital 19. Don't forget to define DHTpin on software:
#define DHTPIN 19
 
CCS811 Connection:
  • Vin goes to 3.3V line,
  • GND goes to GND line,
  • SDA goes to Adalogger SDA
  • SCL goes to Adalogger SCL
  • /WAKE goes to GND
I used Adafruit_CCS811 library version=1.0.0 and everything works fine.

SD Card

To be able to record the values thru time, I used an old 512MB uSD card. This Adalogger board is fantastic because it already has a uSD Card slot. I use here the Arduino library version 1.2.2. and the pin D4 as the CS pin for SD. In the next figure i show the format os the data in SD Card. It is comma separated, in order to be opened by spreadsheet software for analysis.
In this example, data is being recorded every 5 minutes (300 seconds). 
 This value of time is easily changed in software, by changing line:
#define SD_write_interval 300 //seconds between SD writes
Replace 300 by the number of seconds you want between SD writes. That simple.


Schematics

Air Quality Analyzer Connections V2.0

This is the second version of the Air Quality Analyzer Connections. Now we have 3 LEDs to display 3 levels of alarm, and a Sby/On switch. Also DHT11 sensor is connected to a new pin (A5) to avoid some instability caused by pin 13 internal LED circuit.
Picture project diagram v2 mto9qjrayr




Code:

#include <SPI.h> //Standard lib
#include <SD.h> //author=Arduino, SparkFun version=1.2.2
#include <DHT.h>//author=Adafruit version=1.3.0
//#include <Wire.h>
#include <RTCZero.h> //author=Arduino version=1.5.2
#include <RTClib.h> //author=Adafruit version=1.2.1 
#include <Adafruit_GFX.h> //author=Adafruit version=1.2.3
#include <Adafruit_SSD1306.h> //author=Adafruit version=1.1.2
#include <Adafruit_CCS811.h>//author=Adafruit version=1.0.0

//Levels
//temp levels
#define low_temp_level_1 18
#define low_temp_level_2 12
#define low_temp_level_3 8
#define high_temp_level_1 29
#define high_temp_level_2 35
#define high_temp_level_3 40
//rh levels
#define low_rh_level_1 40
#define low_rh_level_2 30
#define low_rh_level_3 20
#define high_rh_level_1 70
#define high_rh_level_2 80
#define high_rh_level_3 90
//co2 levels
#define co2_level_1 1000
#define co2_level_2 1500
#define co2_level_3 3000
//TVOC levels
#define tvoc_level_1 100
#define tvoc_level_2 500
#define tvoc_level_3 1000

//Status LEDS
#define green_led  14
#define yellow_led 15
#define red_led 16

//OLED Display If using software SPI (the default case):
#define OLED_MOSI   6
#define OLED_CLK   10
#define OLED_DC    11
#define OLED_CS    12
#define OLED_RESET 5
#define display_refresh_interval 1 //2 seconds refresh
Adafruit_SSD1306 display(OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);

//DHT Sensor
#define DHTPIN 19     // what pin DHT is connected to (19=A5 on this Board)
#define DHTTYPE DHT11   // DHT 11
#define DHT_read_interval 3 //Read temp every X seconds
DHT dht(DHTPIN, DHTTYPE); //object dht creation

//SD Card
#define cardSelect 4  //SD chip select pin
#define ficheiro "dados.csv" // nome do ficheiro de dados
//#define Serial_Header "Date,Hour,Vbat,Temp,%RH,eCO2,TVOC,STATUS"
#define SD_Header "Date,Hour,Vbat,min_Temp,Max_Temp,min_RH,Max_RH,min_eCO2,Max_eCO2,min_TVOC,Max_TVOC,STATUS"
#define SD_write_interval 300 //seconds between SD writes
#define SD_led 8 //Green LED

//RTC
RTCZero rtc;  // Create an rtc object /

//Vbat read
#define vbatpin A7    //pin to read Bat Voltage
#define vbat_read_interval 30 //seconds between vbat readings
float measuredvbat;

//Set up variables using the SD utility library functions:
Sd2Card card;
SdVolume volume;
SdFile root;

//Set up CCS811 object ccs
Adafruit_CCS811 ccs;
#define CCS811_read_interval 3 //Read data every X seconds

//variable declaration
unsigned long nseconds; //seconds since start of day.
unsigned long SD_last_write = 0;
unsigned long vbat_last_read = 0;
unsigned long DHT_last_read = 0;
unsigned long CCS811_last_read = 0;
unsigned long display_last_refresh = 0;
boolean error = false; //clear errors
boolean alarm = false; //clear alarms
boolean alarm_level_0 = false;
boolean alarm_level_1 = false;
boolean alarm_level_2 = false;
boolean alarm_level_3 = false;
float eco2 = 0.0;
float max_eco2 = 0.0;
float min_eco2 = 10000;
float tvoc = 0.0;
float max_tvoc = 0.0;
float min_tvoc = 10000;
float temp = 0.0;
float max_temp = 0.0;
float min_temp = 100;
float rh = 0.0;
float max_rh = 0.0;
float min_rh = 100;
String errorString = "";
String alarmString = "";
//byte day = 27;
//byte month = 03;
//byte year = 18;
//byte seconds = 00;
//byte minutes = 00;
//byte hours = 12;

void setup() {
  pinMode(SD_led, OUTPUT);// initialize digital pin 8 as an output.
  pinMode(green_led, OUTPUT);// initialize digital pin as an output.
  pinMode(yellow_led, OUTPUT);// initialize digital pin as an output.
  pinMode(red_led, OUTPUT);// initialize digital pin as an output.

  //Serial Port Initialization
  Serial.begin(57600);
  delay (300);//wait for the serial to be ready
  /*
    while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
    }
  */

  //RTC initialization
  DateTime now;
  now = (DateTime(F(__DATE__), F(__TIME__))); //Compilation time
  now=now+20; //compensation for Compile + Reset time
  rtc.begin(); // initialize RTCZero
  rtc.setTime(now.hour(), now.minute(), now.second());
  rtc.setDate(now.day(), now.month(), now.year()-2000);

  //DHT Initialisation
  dht.begin();

  // CCS811 test
  if (!ccs.begin()) {
    Serial.println("Failed to start CCS811 sensor! Please check your wiring.");
    errorString = "CCS811 Error";
    //while (1);
  }
  //calibrate CCS811 temperature sensor
  while (!ccs.available());
  temp = ccs.calculateTemperature();
  ccs.setTempOffset(temp - 25.0);

  //Display initialization
  display.begin(SSD1306_SWITCHCAPVCC);// by default, we'll generate the high voltage from the 3.3v line internally!
  display.clearDisplay();  // Clear the buffer.

  //SD Initialization and Test
  Serial.print("\nInitializing SD card...");
  // we'll use the initialization code from the utility libraries
  // since we're just testing if the card is working!
  if (!card.init(SPI_HALF_SPEED, cardSelect)) {
    Serial.println("initialization failed. Things to check: ");
    Serial.println("* is a card inserted ? ");
    Serial.println("* is your wiring correct ? ");
    Serial.println("* did you change the chipSelect pin to match your shield or module ? ");
    //while (1);
    errorString = "SD Card Error";
  } else {
    Serial.println("Wiring is correct and a card is present.");
  }
  // print the type of card
  Serial.println();
  Serial.print("Card type :         ");
  switch (card.type()) {
    case SD_CARD_TYPE_SD1:
      Serial.println("SD1");
      break;
    case SD_CARD_TYPE_SD2:
      Serial.println("SD2");
      break;
    case SD_CARD_TYPE_SDHC:
      Serial.println("SDHC");
      break;
    default:
      Serial.println("Unknown");
  }
  // Now we will try to open the 'volume'/'partition' - it should be FAT16 or FAT32
  if (!volume.init(card)) {
    Serial.println("Could not find FAT16 / FAT32 partition.\nMake sure you've formatted the card");
    //while (1);
    errorString = "SD Format Error";
  }
  Serial.print("Clusters:          ");
  Serial.println(volume.clusterCount());
  Serial.print("Blocks x Cluster:  ");
  Serial.println(volume.blocksPerCluster());
  Serial.print("Total Blocks:      ");
  Serial.println(volume.blocksPerCluster() * volume.clusterCount());
  Serial.println();
  // print the type and size of the first FAT-type volume
  uint32_t volumesize;
  Serial.print("Volume type is:    FAT");
  Serial.println(volume.fatType(), DEC);
  volumesize = volume.blocksPerCluster();    // clusters are collections of blocks
  volumesize *= volume.clusterCount();       // we'll have a lot of clusters
  volumesize /= 2;                           // SD card blocks are always 512 bytes (2 blocks are 1KB)
  Serial.print("Volume size (Kb):  ");
  Serial.println(volumesize);
  Serial.print("Volume size (Mb):  ");
  volumesize /= 1024;
  Serial.println(volumesize);
  Serial.print("Volume size (Gb):  ");
  Serial.println((float)volumesize / 1024.0);
  Serial.println("\nFiles found on the card (name, date and size in bytes): ");
  root.openRoot(volume);
  // list all files in the card with date and size
  root.ls(LS_R | LS_DATE | LS_SIZE);
  if (!SD.begin(cardSelect)) {
    errorString = ("No SD Card");
  }
  else {
    File logfile = SD.open(ficheiro, FILE_WRITE);
    if (!logfile)
      errorString = ("File Error");
    else {
      Serial.print("Gravando em: ");
      Serial.println(ficheiro);
      logfile.println(SD_Header);
      logfile.close();
    }
  }
}

void loop() {
  //How many seconds since 0H (start of day)
  nseconds = 3600 * rtc.getHours() + 60 * rtc.getMinutes() + rtc.getSeconds();

  //measure Vbat
  if (abs(nseconds - vbat_last_read >= vbat_read_interval)) //time to read vbat
  {
    measuredvbat = analogRead(vbatpin);
    measuredvbat *= 2; // we divided by 2, so multiply back
    measuredvbat *= 3.3; // Multiply by 3.3V, our reference voltage
    measuredvbat /= 1024; // convert to voltage
    vbat_last_read = nseconds;
  }
  // Reading temperature and humidity
  //if (nseconds % DHT_read_interval == 0) //Time to read temp
  if (abs(nseconds - DHT_last_read >= DHT_read_interval)) //time to read DHT
  {
    rh = dht.readHumidity();
    temp = dht.readTemperature();  // Read temperature as Celsius (the default)
    float f = dht.readTemperature(true);   // Read temperature as Fahrenheit (isFahrenheit = true)

    if (isnan(rh) || isnan(temp) || isnan(f)) {
      Serial.println("Failed to read from DHT sensor!");// Check if any reads failed and exit early (to try again).
      error = true;
      errorString = ("DHT Error");
      //return;
    }
    DHT_last_read = nseconds;
  }
  // Reading eCO2 & TVOC
  //if (nseconds % CCS811_read_interval == 0) //Time to read data
  if (abs(nseconds - CCS811_last_read >= CCS811_read_interval)) //time to read CCS811
  {
    if (ccs.available()) {
      //temp = ccs.calculateTemperature();
      if (!ccs.readData()) {
        eco2 = ccs.geteCO2();
        tvoc = ccs.getTVOC();
      }
      else {
        error = true;
        Serial.println("CCS811 sensor error!");
        errorString = "CCS811 error";
        //while(1);
      }
      CCS811_last_read = nseconds;
    }
  }

  //Alarm levels
  alarm_level_3 = temp > high_temp_level_3 || temp < low_temp_level_3 || rh > high_rh_level_3 || rh < low_rh_level_3 || eco2 > co2_level_3 || tvoc > tvoc_level_3;
  alarm_level_2 = temp > high_temp_level_2 || temp < low_temp_level_2 || rh > high_rh_level_2 || rh < low_rh_level_2 || eco2 > co2_level_2 || tvoc > tvoc_level_2;
  alarm_level_1 = temp > high_temp_level_1 || temp < low_temp_level_1 || rh > high_rh_level_1 || rh < low_rh_level_1 || eco2 > co2_level_1 || tvoc > tvoc_level_1;
  alarm_level_0 = !alarm_level_3 & !alarm_level_2 & !alarm_level_2;

  //Alarm LEDS &  Alarm Strings
  if (alarm_level_3) {
    alarm = true;
    digitalWrite(green_led, LOW);
    digitalWrite(yellow_led, LOW);
    digitalWrite(red_led, HIGH);
    if (eco2 > co2_level_3) alarmString = "CO2 High ++";
    else if (tvoc > tvoc_level_3) alarmString = "TVOC High ++";
    else if (rh > high_rh_level_3) alarmString = "RH High ++";
    else if (rh < low_rh_level_3) alarmString = "RH Low --";
    else if (temp > high_temp_level_3) alarmString = "Temp High ++";
    else if (temp < low_temp_level_3) alarmString = "Temp Low --";
  }
  else if (alarm_level_2) {
    alarm = true;
    digitalWrite(green_led, LOW);
    digitalWrite(yellow_led, HIGH);
    digitalWrite(red_led, LOW);
    if (eco2 > co2_level_2) alarmString = "CO2 High +";
    else if (tvoc > tvoc_level_2) alarmString = "TVOC High +";
    else if (rh > high_rh_level_2) alarmString = "RH High +";
    else if (rh < low_rh_level_2) alarmString = "RH Low -";
    else if (temp > high_temp_level_2) alarmString = "Temp High +";
    else if (temp < low_temp_level_2) alarmString = "Temp Low -";
  }
  else if (alarm_level_1) {
    alarm = true;
    digitalWrite(green_led, LOW);
    digitalWrite(yellow_led, HIGH);
    digitalWrite(red_led, LOW);
    if (eco2 > co2_level_1)alarmString = ("CO2 High");
    else if (tvoc > tvoc_level_1) alarmString = "TVOC High";
    else if (rh > high_rh_level_1) alarmString = "RH High";
    else if (rh < low_rh_level_1) alarmString = "RH Low";
    else if (temp > high_temp_level_1) alarmString = "Temp High";
    else if (temp < low_temp_level_1) alarmString = "Temp Low";
  }
  else if (alarm_level_0) {
    alarm = false;
    digitalWrite(green_led, HIGH);
    digitalWrite(yellow_led, LOW);
    digitalWrite(red_led, LOW);
    alarmString = "OK";
  }
  //Max and min determination
  if (temp > max_temp) max_temp = temp;
  if (temp < min_temp) min_temp = temp;
  if (rh > max_rh) max_rh = rh;
  if (rh < min_rh) min_rh = rh;
  if (eco2 > max_eco2) max_eco2 = eco2;
  if (eco2 < min_eco2) min_eco2 = eco2;
  if (tvoc > max_tvoc) max_tvoc = tvoc;
  if (tvoc < min_tvoc) min_tvoc = tvoc;

  //Relatorio serial
  Serial.print("Data:" );
  serial2digits(rtc.getDay());
  Serial.print("/");
  serial2digits(rtc.getMonth());
  Serial.print("/");
  Serial.print(2000 + rtc.getYear(), DEC);
  Serial.print(" Hora:" );
  serial2digits(rtc.getHours());
  Serial.print(":");
  serial2digits(rtc.getMinutes());
  Serial.print(":");
  serial2digits(rtc.getSeconds());
  Serial.print("  ");
  Serial.print("VBat:" ); Serial.print(measuredvbat);
  Serial.print(" Temp:");   Serial.print(temp);
  Serial.print(" Humid:");   Serial.print(rh);
  Serial.print(" CO2:");   Serial.print(eco2);
  Serial.print(" ppm, TVOC:");  Serial.print(tvoc); Serial.print(" ppb:");
  Serial.print(" Status:");
  if (error)
    Serial.println(errorString);
  else if (alarm)
    Serial.println(alarmString);
  else Serial.println("OK");

  //Data display
  if (abs(nseconds - display_last_refresh >= display_refresh_interval)) //time to display information
  {
    display.clearDisplay();// Clean Display
    display.setTextSize(1);
    display.setTextColor(WHITE);
    display.setCursor(0, 0);
    display.println("Air Quality Analyzer");
    display.setCursor(0, 8);
    display2digits(rtc.getDay());
    display.print("/");
    display2digits(rtc.getMonth());
    display.print("/");
    display.print(2000 + rtc.getYear(), DEC);
    display.print(" ");
    display2digits(rtc.getHours());
    display.print(":");
    display2digits(rtc.getMinutes());
    /*display.print(":");
      display.print(rtc.getSeconds(), DEC);*/
    display.setCursor(0, 16);
    display.print("Vbat:");
    display.setCursor(50, 16);
    display.print(measuredvbat); display.print(" V");
    display.setCursor(0, 24);
    display.print("Temp:");
    display.setCursor(50, 24);
    display.print(temp); display.print(" C");
    display.setCursor(0, 32);
    display.print("RH:");
    display.setCursor(50, 32);
    display.print(rh); display.print(" %");
    display.setCursor(0, 40);
    display.print("eCO2:");
    display.setCursor(50, 40);
    display.print(eco2); display.print(" ppm");
    display.setCursor(0, 48);
    display.print("TVOC:");
    display.setCursor(50, 48);
    display.print(tvoc); display.print(" ppb");
    display.setCursor(0, 56);
    display.print("Status:");
    display.setCursor(50, 56);
    if (error) {
      display.print(errorString);
    } else if (alarm) {
      display.print(alarmString);
    }
    else
    {
      display.print("OK");
    }
    display.display();
    display_last_refresh = nseconds;
  }
  //SD datalog
  //if (nseconds % SD_write_interval == 0) //Time to log data
  if (abs(nseconds - SD_last_write >= SD_write_interval)) //Time to log data
  {
    File logfile = SD.open(ficheiro, FILE_WRITE);
    if (logfile) {
      digitalWrite(SD_led, HIGH); //SD Write begins
      logfile2digits(rtc.getDay(), logfile);
      logfile.print("/");
      logfile2digits(rtc.getMonth(), logfile);
      logfile.print("/");
      logfile.print(2000 + rtc.getYear(), DEC);
      logfile.print(",");
      logfile2digits(rtc.getHours(), logfile);
      logfile.print(":");
      logfile2digits(rtc.getMinutes(), logfile);
      logfile.print(":");
      logfile2digits(rtc.getSeconds(), logfile);
      logfile.print(",");
      logfile.print(measuredvbat, 2);
      logfile.print(",");
      logfile.print(min_temp, 2);
      logfile.print(",");
      logfile.print(max_temp, 2);
      logfile.print(",");
      logfile.print(min_rh, 2);
      logfile.print(",");
      logfile.print(max_rh, 2);
      logfile.print(",");
      logfile.print(min_eco2, 2);
      logfile.print(",");
      logfile.print(max_eco2, 2);
      logfile.print(",");
      logfile.print(min_tvoc, 2);
      logfile.print(",");
      logfile.print(max_tvoc, 2);
      logfile.print(",");
      if (error)
        logfile.println(errorString);
      else if (alarm)
        logfile.println(alarmString);
      else logfile.println("OK");
      logfile.close();
      SD_last_write = nseconds;
      digitalWrite(SD_led, LOW); //SD Write ends
    }
    else
    {
      error = true;
      errorString = ("logfile error");
    }
    //Max and min RESET
    max_eco2 = 0.0;
    min_eco2 = 10000;
    max_tvoc = 0.0;
    min_tvoc = 10000;
    max_temp = 0.0;
    min_temp = 100;
    max_rh = 0.0;
    min_rh = 100;
  }
  delay(200); //don't use values higher than 1000 or nseconds won't be properly updated
}

void display2digits(int number)// create a leading zero for month/day/hour/minute when <10
{
  if (number >= 0 && number < 10) {
    display.write('0');
  }
  display.print(number);
}

void logfile2digits(int number, File logfile) // create a leading zero for month/day/hour/minute when <10
{
  if (number >= 0 && number < 10) {
    logfile.write('0');
  }
  logfile.print(number);
}

void serial2digits(int number)// create a leading zero for month/day/hour/minute when <10
{
  if (number >= 0 && number < 10) {
    Serial.write('0');
  }
  Serial.print(number);
}