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:

El programa és el següent:

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

El programa és el següent:


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

El programa és el següent:

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

El programa és el següent:



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