Internet de les coses amb ESP32 i ESP8266

Exemples Referència Plaques   Recursos CITCEA
Projectes Programació Perifèrics   Inici

Sensor de compostos orgànics volàtils

Programa del grup 2

En aquest cas s'ha dissenyat un mesurador que es comunica amb un script de Google, el qual guarda les dades en dos fulls de càlcul. En un dels fulls (que sempre és el mateix) es guarden les dades més recents mentre que l'altre canvia diàriament per tal de tenir les dades separades per dies i classificades per mesos i anys.

El programa del microcontrolador és el següent:

// ----------------Càrrega de biblioteques--------------------
#include <SPI.h>  // Carreguem la biblioteca SPI
#include <WiFiNINA.h>  // Carreguem la biblioteca WiFiNINA
#include "DHT.h"
#include <Wire.h>
#include "Adafruit_SGP30.h"

#define DHTPIN 2
#define DHTTYPE DHT22
#define server_len 50
#define pag_len 400
// --------------Definició de variables globals--------------------
int compta = 0;
uint16_t TCOV_base, eCO2_base;
Adafruit_SGP30 sensor;
const char idXarxa[] = "xarxa-wifi";  // Nom del punt d'accés 
const char contrasenya[] = "contrasenya-wifi";  // Contrasenya de connexió 
const String server0 = "script.google.com";
String pagina0 = "";
const String pagina_base = "/macros/s/^^fycbxOL34xEXMqgf0WO3E9hPeSc4q_uC3iihl1mggkLXl0sNEDRSY4aRiXLAfcybo7VRE-/exec?";
char server[server_len]; 
char pagina[pag_len];
unsigned long darreraConnexio = 0;
const unsigned long periodeConnexio = 100UL;
bool pendent, completa, redir;
bool ara = false;
float hum, temp, hidrogen, etanol, tvoc, eco;
String data;
String peticio = "";  // Aquí guardarem una línia de la petició del client
String peticioAux = "";  // i la petició anterior (també ho farem servir de reserva)
int status = WL_IDLE_STATUS;
WiFiSSLClient client;
DHT dht(DHTPIN, DHTTYPE);
// ----------------------Programa de inicialització-------------------------
void setup() {  // Inicialització
  Serial.begin(9600);  // Monitor sèrie
  dht.begin();
  if (! sensor.begin()) { // Mira si troba el sensor
    Serial.println("Sensor no trobat!");
    while (1);
  }
  if (WiFi.status() == WL_NO_MODULE) {
    Serial.println("No s'ha trobat el dispositiu Wi-Fi");
    while (true);  // Bloquegem el programa
  }
  String versio = WiFi.firmwareVersion();
  if (versio < "1.0.0") {
    Serial.println("Convindria actualitzar el firmware");
  }
  while (status != WL_CONNECTED) {
    Serial.print("Connectant a la xarxa ");
    Serial.println(idXarxa);
    status = WiFi.begin(idXarxa, contrasenya); // xarxa WPA o WPA2
    delay(10000);  // Ho tornarem a intentar passats 10 s
  }
  Serial.print("Connectat a "); 
  Serial.println(WiFi.SSID());
  Serial.print("Estat de la connexió: ");
  Serial.println(WiFi.status()); 
  Serial.print("Adreça IP del dispositiu: ");
  Serial.println(WiFi.localIP()); 
  Serial.print("Intensitat del senyal: ");
  Serial.print(WiFi.RSSI()); 
  Serial.println(" dBm");
  Serial.println(); 
  redir = false;
}
// -----------------------Bucle del programa-----------------------------
void loop() {  // Programa que es repeteix indefinidament
  // El bucle principal té tres parts: 
  //  1. Gestió dels caràcters que arriben
  //  2. Tractament de les dades rebudes
  //  3. Nova petició quan ha passat el temps
  while (client.available()) {
    // Gestió dels caràcters que arriben
    // Aquest bucle va guardant els caràcters rebuts
    // i espera al moment en que arriba un salt de línia
    char c = client.read();  // Rebem caràcters del servidor
    if (c == '\n') {  // Mirem si és un salt de línia
      peticioAux = peticio;  // Guardem la petició anterior
      peticio = "";  // Ens preparem per a la línia següent
      completa = true;  // Preparat per escriure-ho
    } else {
      peticio += c;  // Afegim el caràcter rebut
    }
    // Quan ha arribat un salt de línia, hem de mirar què ha arribat
    if (completa){  // Ha arribat una línia completa
      if (peticioAux.startsWith("HTTP/1.1 200")){  // Resposta bona
        pendent = true;
      }
      if (peticioAux.startsWith("HTTP/1.1 302")){  // Redireccionament
        redir = true;
      }
      if (redir && (peticioAux.startsWith("Location:"))){
        // Si hi ha redireccionament, hem de buscar l'adreça
        // i extreure'n el servidor i la pàgina
        String adre = peticioAux.substring(peticioAux.indexOf("// ") +2);
        String server1 = adre.substring(0, adre.indexOf(".com") +4);
        String pagina1 = adre.substring(adre.indexOf(".com") +4);
        server1.toCharArray(server, 50); // Copia l'Array server.1 a server i retorna un Char
        pagina1.toCharArray(pagina, 400);
        ara = true;
      }
      completa = false;
    }
  }
  // Hi ha una resposta per processar
  // Però ignorarem les dades que ens envia l'script
  if (pendent) {
    pendent = false;
  }
  // Quan toca, tornem a fer una petició
  if (ara || ((millis() - darreraConnexio > periodeConnexio))) {
    hum = dht.readHumidity();
    temp = dht.readTemperature();
    if (sensor.IAQmeasure() && sensor.IAQmeasureRaw()){
      hidrogen = sensor.rawH2;
      etanol = sensor.rawEthanol;
      tvoc = sensor.TVOC;
      eco = sensor.eCO2;
    } else {
      Serial.println("Ha fallat la lectura!");
      return;
    }
    data = "t=";
    data += temp;
    data += "&h=";
    data += hum;
    data += "&hidr=";
    data += hidrogen;
    data += "&et=";
    data += etanol;
    data += "&tvoc=";
    data += tvoc;
    data += "&eco=";
    data += eco;
    if (redir){
      redir = false;
    } else {
      pagina0 = pagina_base + data;
      server0.toCharArray(server, server_len); // Posa el nom del servidor
      pagina0.toCharArray(pagina, pag_len);
    }
    ara = false;
    client.stop();
    if (client.connect(server, 443)) {
      Serial.println("S'ha fet la connexió al servidor");
      client.print("GET ");
      client.print(pagina);
      client.println(" HTTP/1.1");
      client.print("Host: ");
      client.println(server);
      client.println("Connection: close");
      client.println();
      // Guardem quan hem fet la connexió
      darreraConnexio = millis();
      Serial.print("Enviat: ");
      Serial.println(data);
    } else {
      Serial.println("connection failed");
    }
  }
}

El programa de l'script és el següent:

// Identificador del full de càlcul principal que conté els gràfics. 
var IdFull = "^^95lNrrBYarzr3g9WXMimlZ80wwwtjGMU-fSs0yT9Mo"; 
// Carreguem la data d'avui i l'hora
var avui = new Date();
var Hora = avui.getHours();
var Minut = avui.getMinutes();
var Temps = Hora + ":" + Minut + " h";
var year = avui.getFullYear();
var mes = avui.getMonth();
var dia = avui.getDate();
var mesos = ['Gener','Febrer','Març','Abril','Maig','Juny','Juliol','Agost',
  'Setembre','Octubre','Novembre','Desembre'];
// Número del full i nombre de columnes que té
var numFull = 0; 
var numCols = 7;
// Funció per crear una full de càlcul corresponent al dia d'avui
// i classificar les dades en funció de l'any, el mes i el dia.
function busca_full(){
  // En aquest cas, el lloc on es gestionen els arxius és la carpeta "PROJECTE I". Té com a objectiu
  // representar el compte de Google Drive de l'usuari del producte
  var carpeta_P = DriveApp.getFoldersByName("PROJECTE I").next();
  var mes2 = mesos[mes];
  // Mirem si hi ha la carpeta "Dades". És on es classifica tota la informació.
  if (carpeta_P.getFoldersByName("Dades").hasNext()==false){
    // Si no n'hi ha cap, crea una de nova.
    var carpetaDades = carpeta_P.createFolder("Dades"); 
  } else { 
    var carpetaDades = carpeta_P.getFoldersByName("Dades").next();
  }
  // Mirem si hi ha una carpeta de l'any actual a Dades.
  if (carpetaDades.getFoldersByName(year).hasNext()==false){
    // Si no n'hi ha cap, crea una de nova.
    var carpetayear = carpetaDades.createFolder(year); 
  } else {
    var carpetayear=carpetaDades.getFoldersByName(year).next();
  }
  // Mirem si dins la carpeta hi ha un SpreadSheet del mes actual.
  if (carpetayear.getFilesByName(mes2).hasNext()==false){
    // Si no n'hi ha cap, crea un de nou.
    SpreadsheetApp.create(mes2);
    var arxiu_mes = DriveApp.getFilesByName(mes2).next();
    arxiu_mes.moveTo(carpetayear); 
  } else {
    var arxiu_mes=carpetayear.getFilesByName(mes2).next();
  }
  // Obrim l'SpreadSheet del mes actual
  var Idmes = arxiu_mes.getId();
  var full_mes=SpreadsheetApp.openById(Idmes);
  // Mirem si dins l'SpreadSheet hi ha un full del dia actual
  if (full_mes.getSheetByName(dia)==null){
    // Si no n'hi ha cap, insereix un de nou.
    var full_dia = full_mes.insertSheet(dia.toString()); 
  } else {
    var full_dia = full_mes.getSheetByName(dia.toString());
  };
  // Escribim els títols
  full_dia.getRange("A1:G1").setValues([["Temps","Temperatura","Humitat","Hidrogen","Etanol","TVOC","e-CO2"]]);
  var hoja1=full_mes.getSheetByName("Hoja 1");
  // Eliminem el full que té per nom "Hoja 1"
  if (hoja1!=null){
    full_mes.deleteSheet(hoja1);
  };
  // Retornem l'identificador del SpreadSheet
  return Idmes
}
// Funció que permet escriure les dades enviades pel 
// microcontrolador en els fulls de càlcul
function doGet(e) {
  // Carreguem l'identificador del SpreadSheet del mes actual
  var Idmes=busca_full();
  var resultat = '';
  var camps = new Array(6);  // Valors per guardar a la taula
  // Assignem els paràmetres a variables
  var Accio = e.parameter.acc;
  var Temp = e.parameter.t
  var Hum = e.parameter.h;
  var Hidr = e.parameter.hidr;
  var Etanol = e.parameter.et;
  var Toc = e.parameter.tvoc;
  var Equivalent = e.parameter.eco;
  if (Accio==undefined){
    var TempString = Temp.replace(".",",");
    var HumString = Hum.replace(".",",");
    var HidrString = Hidr.replace(".",",");
    var EtanolString = Etanol.replace(".",",");
    var TocString = Toc.replace(".",",");
    var EquivalentString = Equivalent.replace(".",",");
    if ((Temp == undefined) || (Hum == undefined)){
      resultat = 'Falten paràmetres';
    } else {
      camps[0] = Temps;
      camps[1] = TempString;
      camps[2] = HumString;
      camps[3] = HidrString;
      camps[4] = EtanolString;
      camps[5] = TocString;
      camps[6] = EquivalentString;
      // Afegim la informació al full que conté totes les dades
      var sh=SpreadsheetApp.openById(IdFull);
      var sheet = sh.getSheets();
      // No poden haver-hi més de cinquanta-una files ocupades
      var ultimafila=sheet[numFull].getLastRow();
      if (ultimafila==51){
        sheet[numFull].deleteRow(2);
      }
      sheet[numFull].appendRow(camps);
      // Afegim la informació al full del dia actual
      var sheet2=SpreadsheetApp.openById(Idmes).getSheetByName(dia);
      sheet2.appendRow(camps);  
      resultat = 'Valors guardats';
    }
  }
  // Instruccions que permeten consultar la informació mitjançant
  // l'aplicació de MIT App Inventor 2
  if (Accio == "read"){
    var sh = SpreadsheetApp.openById(IdFull);
    var sheet = sh.getSheets();
    // Llegim les dades del full seleccionat
    var full = sheet[numFull].getDataRange().getValues();
    range = sheet[numFull].getLastRow();  // Índex de la darrera filera (+1 pels títols)
    if (range > 1){  // Si és 1 només hi ha els títols
      var filera = full[range -1];  // Agafem la darrera filera
      var resultat = '';
      for(var j = 0;j < numCols;j++){ 
        if (j > 0){
          resultat = resultat + '/';  // La barra separarà els valors
        }
        resultat = resultat + filera[j];
      }
    }
  }
  return ContentService.createTextOutput(resultat); 
}

L'aplicació feta amb App Inventor té quatre pantalles. La disposició d'elements a la primera pantalla és:

Pantalla       Pantalla

El programa és el següent:

Programa

La disposició d'elements a la segona pantalla és:

Pantalla       Pantalla

El programa és el següent:

Programa
Programa

La disposició d'elements a la tercera pantalla és:

Pantalla

El programa és el següent:

Programa

La disposició d'elements a la quarta pantalla és:

Pantalla

El programa és el següent:

Programa
Programa

 

 

Llicència de Creative Commons
Aquesta obra d'Oriol Boix està llicenciada sota una llicència no importada Reconeixement-NoComercial-SenseObraDerivada 3.0.