Internet de les coses amb ESP32 i ESP8266

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

Consultem les cites de Google Calendar fent servir un script

Google Calendar ens permet tenir un calendari amb les nostres cites. Aquestes poden ser consultades, mitjançant un script, des del nostre microcontrolador. També podem fer servir un calendari per indicar a quines hores s'ha d'activar un dispositiu. Anem a veure un exemple de com podem fer un script que llegeixi les cites que hi ha en el calendari per a la data d'avui.

Començarem per entrar a la pàgina de Google Calendar. Si el navegador es recorda del nostre usuari ja podrem veure els calendaris associats al nostre compte. En cas contrari, ens haurem d'identificar. Per fer proves ens ha semblat més adequat fer servir un calendari específic, així podem crear i esborrar esdeveniments al nostre gust. També podríem fer servir un calendari ja existent.

En la part superior de la pàgina de Google Calendar busquem un botó en forma d'engranatge i, en el desplegable, triem l'opció Configuració.

Configuració

Despleguem l'opció Afegeix un calendari i piquem a Crea un calendari.

Crea un calendari

Li posem un nom i creem el calendari. Un cop creat el calendari, aquest ens apareixerà al menú de l'esquerra. Despleguem les opcions per al nostre calendari i triem l'opció Integra el calendari.

Integra el calendari

Ens mostrarà, a la dreta, una pàgina amb diverses opcions i hem de trobar la casella Identificador de calendari on trobarem un codi similar a rsp0fv05mt9ia5lntfpuj32mmc@group.calendar.google.com que és l'identificador del nostre calendari. Un cop tenim això, ja podem començar a crear el nostre script.

A continuació anirem a la pàgina de Google Drive (se suposa que ja estem identificats) i picarem el botó següent.

Botó nou

Si encara no hem creat cap script, l'opció de crear-ne un no estarà disponible directament. Piquem al botó més i, si tampoc veiem l'opció, anem a Connecta més aplicacions. Podem fer servir el cercador per trobar (posant-hi la paraula app) l'aplicació Google Apps Script.

Google Apps Script

Un cop l'haguem trobat, picarem el botó Connecta. Ara ja podem crear un nou script. Se'ns obrirà una finestra similar a la següent:

Vista del programa

Hem d'esborrar la funció buida myFunction i deixar l'espai en blanc. Aquest és l'script que farem servir:

// Funció per interaccionar amb el calendari des del microcontrolador
// Oriol Boix, 2019
// Sota llicència Creative Commons BY-NC-ND
// https://creativecommons.org/licenses/by-nc-nd/3.0/deed.es_ES
//
// Les variables següents ens permeten personalitzar l'script al nostre projecte
// En principi, no hauríem de tocar la resta de l'script 
var idCal = "rsp0fv05mt9ia5lntfpuj32mmc@group.calendar.google.com";
// Script per interactuar amb el calendari
// Funció que s'executa quan hi ha una ordre get
function doGet(e) {
  var salt = "\n";
  var cal = CalendarApp.getCalendarById(idCal);
  if (!cal) {  // Si el calendari no existeix o no tenim permís
    resultat = "Calendari no trobat!";
    return ContentService.createTextOutput(resultat);
  }
  var ara = new Date();  // La data i l'hora del moment d'executar l'script
  var final = new Date();
  final.setHours(23);  // Li canviem l'hora a les 23.59
  final.setMinutes(59);
  // Agafem tots els esdeveniments des d'ara (inclosos els ja iniciats) fins les 23.59 h
  var esdev = cal.getEvents(ara, final);
  var numEsdev = esdev.length;  // Quants n'hi ha?
  var resultat = "";
  if (numEsdev > 0){  // Hi ha, com a mínim, un esdeveniment
    resultat = resultat + dades(esdev[0]);  // Primer esdeveniment
  }
  if (numEsdev > 1){  // Hi ha, com a mínim, un segon  esdeveniment
    resultat = resultat + salt + dades(esdev[1]);  // Segon esdeveniment
  }
  return ContentService.createTextOutput(resultat);  // Enviem la resposta
}
function dades(esdAct) {  // Organitza les dades d'un esdeveniment
  var descrip = esdAct.getTitle();  // Títol de l'esdeveniment
  var dataIni = esdAct.getStartTime();  // Data i hora d'inici
  var dataFi = esdAct.getEndTime();  // Data i hora d'acabament
  // Ens interessen només les hores d'inici i acabament
  // i les volem en el format habitual en català
  var ini = dataIni.getHours() + "." + dataIni.getMinutes();
  var fi = dataFi.getHours() + "." + dataFi.getMinutes();
  var resul = descrip + "," + ini + "," + fi;
  return resul;
}

Copiem tot el text i l'enganxem a la finestra de l'editor d'scripts. Un cop enganxat, haurem de fer-hi un canvi. En la imatge següent s'ha requadrat el codi de la taula que caldrà canviar pel que correspongui.

Codi de la taula

Un cop personalitzat el programa l'hem de guardar, picant el botó que es mostra a continuació.

Botó guardar

En el menú, anirem a la pestanya Publica i triarem l'opció Implementa com a aplicació web... i s'obrirà una finestra similar a la següent:

Finestra

En aquesta pantalla hem d'anar al desplegable on diu Qui té accés a l'aplicació i hem de triar l'opció Qualsevol usuari, fins i tot els anònims. També hem de cercar, a la part superior, l'adreça URL corresponent al nostre script i copiar-la.

URL script https://script.google.com/macros/s/AKfycbxqrJpVA-KT1sUd8HIta643R3bH4ixpDahttayGSGjkpHUBjPQ/exec

Un cop estiguem, podem picar el botó Actualitza.

Atenció: Cada cop que modifiquem el codi del nostre script l'hem de salvar i tornar-lo a publicar. En el desplegable Versió del projecte: cal triar cada vegada l'opció Nou. Si no ho fem així s'executarà la versió anterior del codi, sense tenir en compte les modificacions.

Els scripts es poden provar amb el navegador web. Si tenim un calendari amb les següents activitats

Calendari

i posem l'adreça URL a la barra d'adreces del navegador, aquest script ens tornarà la següent resposta:

Tasca 1,17.15,18.15
Tasca 2,19.0,20.0
 

Aquesta resposta és molt fàcil d'analitzar. La primera línia correspon al primer esdeveniment i la segona la segon. A cada esdeveniment podem cercar les comes per poder separar el nom de l'esdeveniment, l'hora d'inici i l'hora d'acabament. Si calgués, podem separar les hores i els minuts cercant el punt.

El següent programa s'encarrega de demanar a l'script que ens enviï la informació dels propers esdeveniments i la processa per mostrar-la pel canal sèrie. Està basat en l'exemple del NeoPixel amb un script i, per tant, hi ha coses que ja no les comentarem perquè estan explicades allà. Aquest programa pot necessitar força memòria de dades. Atès que aquesta és relativament limitada, hem aplicat algunes mesures per reduir la quantitat de memòria ocupada.

// Aquest programa està parcialment basat en els exemples de la pàgina
// https://www.arduino.cc/en/Tutorial/LibraryExamples#wifi1010
#include <SPI.h>    // Carreguem la biblioteca SPI
#include <WiFiNINA.h>    // Carreguem la biblioteca WiFiNINA
#define server_len 50
#define pag_len 400
#define mis_len 2  // Nombre màxim de línies de la resposta
const char idXarxa[] = "xarxa-wifi";    // Nom del punt d'accés 
const char contrasenya[] = "contrasenya-wifi";    // Contrasenya de connexió 
char server[server_len];
char pagina[pag_len];
unsigned long darreraConnexio = 0;
const unsigned long periodeConnexio = 10000UL;
bool pendent, completa, redir;
bool ara = false;
bool ini_msg = false;
int comp_lin = 0;
String peticio = "";    // Aquí guardarem una línia de la petició del client
String peticioAux = "";    // la petició anterior (també ho farem servir de reserva)
String missatge[mis_len];  // Aquí hi guardarem el text rebut
String server1, pagina1;
int status = WL_IDLE_STATUS;
WiFiSSLClient client;
void processa(String missat){
    String Acte, Inici, Final;    // Aquí guardarem les dades
    Acte = missat.substring(0, missat.indexOf(","));
    missat = missat.substring(missat.indexOf(",") +1);
    Inici = missat.substring(0, missat.indexOf(","));
    Final = missat.substring(missat.indexOf(",") +1);
    Serial.print("Acte: ");
    Serial.println(Acte);
    Serial.print("Inici: ");
    Serial.println(Inici);
    Serial.print("Final: ");
    Serial.println(Final);
}
void setup() {    // Inicialització
    Serial.begin(9600);    // Monitor sèrie
    while (!Serial) {
        ;    // Esperem que l'usuari obri el monitor sèrie
    }
    if (WiFi.status() == WL_NO_MODULE) {
        Serial.println(F("No s'ha trobat el dispositiu Wi-Fi"));
        // La funció F obliga al compilador a guardar el text a la memòria de programa
        while (true);    // Bloquegem el programa
    }
    String versio = WiFi.firmwareVersion();
    if (versio < "1.0.0") {
        Serial.println(F("Convindria actualitzar el firmware"));
    }
    while (status != WL_CONNECTED) {
        Serial.print(F("Connectant a la xarxa "));
        Serial.println(idXarxa);
        status = WiFi.begin(idXarxa, contrasenya);
        delay(10000);    // Ho tornarem a intentar passats 10 s
    }
    Serial.print(F("Connectat a ")); 
    Serial.println(WiFi.SSID());
    Serial.print(F("Estat de la connexió: "));
    Serial.println(WiFi.status()); 
    Serial.print(F("Adreça IP del dispositiu: "));
    Serial.println(WiFi.localIP()); 
    Serial.print(F("Intensitat del senyal: "));
    Serial.print(WiFi.RSSI()); 
    Serial.println(F(" dBm"));
    Serial.println(); 
    Serial.println(F("Anem a connectar al servidor"));
    redir = false;
}
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
            //Serial.println(peticioAux);
            peticio = "";    // Ens preparem per a la línia següent
            completa = true;    // Preparat per tractar-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
            // El nostre script envia un salt de línia al final i, per tant, 
            // totes les dades les podrem anar agafant de peticioAux
            // Si la darrera tasca no tingués salt de línia no ens arribaria 
            // aquí però estaria guardada a peticio
            if ((ini_msg) && (comp_lin < mis_len) && (peticioAux.length() > 1)){
                // Si la línia ja és la resposta la guardem
                missatge[comp_lin++] = peticioAux;
            }
            if (peticioAux.startsWith(F("HTTP/1.1 200"))){    // Resposta bona
                pendent = true;
                redir = false;
            }
            if (peticioAux.startsWith(F("HTTP/1.1 302"))){    // Redireccionament
                redir = true;
            }
            if (redir && (peticioAux.startsWith(F("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);
                server1 = adre.substring(0, adre.indexOf(".com") +4);
                pagina1 = adre.substring(adre.indexOf(".com") +4);
                server1.toCharArray(server, server_len);
                pagina1.toCharArray(pagina, pag_len);
                ara = true;
            }
            if (pendent && peticioAux.startsWith(F("Connection: close"))){
                ini_msg = true;
            }
            completa = false;
        }
    }
    // Hi ha una resposta per processar
    if (pendent) {
        pendent = false;
        if (comp_lin > 0){
            for (byte k = 0; k < mis_len; k++){
                if (missatge[k].length() > 1){    // Si no està buit
                    Serial.print(F("--- Tasca "));
                    Serial.print(k + 1);
                    Serial.println(F(" ---"));
                    processa(missatge[k]);
                    Serial.println();
                }
            }
        } else {
            Serial.println(F("No queden tasques per al dia d'avui"));
        }

    }
    // Quan toca, tornem a fer una petició
    if (ara || ((millis() - darreraConnexio > periodeConnexio))) {
        ini_msg = false;
        comp_lin = 0;
        for (byte k = 0; k < mis_len; k++){
            missatge[k] = "";
        }        
        if (!redir){
            server1 = "script.google.com";
            pagina1 = "/macros/s/AKfycbzRfUy5m23ywc7UWqWqhx8UTJJVsdUoFtJSgOkBdVlp1TbLSZDd/exec";
            server1.toCharArray(server, server_len);
            pagina1.toCharArray(pagina, pag_len);
        }
        ara = false;
        client.stop();
        if (client.connect(server, 443)) {
            Serial.println(F("S'ha fet la connexió al servidor"));
            client.print(F("GET "));
            client.print(pagina);
            client.println(F(" HTTP/1.1"));
            client.print(F("Host: "));
            client.println(server);
            client.println(F("Connection: close"));
            client.println();
            // Guardem quan hem fet la connexió
            darreraConnexio = millis();
        } else {
            Serial.println(F("connection failed"));
        }
    }
}

Per a les dues tasques que hem comentat més amunt, el programa ens donaria la següent resposta en el monitor sèrie:

S'ha fet la connexió al servidor
--- Tasca 1 ---
Acte: Prova1
Inici: 17.15
Final: 18.15

--- Tasca 2 ---
Acte: Prova2
Inici: 19.0
Final: 20.0

En aquest programa hi ha la condició que el monitor sèrie estigui obert per a que el programa comenci a funcionar. Això ens permet seguir tot el procés encara que triguem a obrir el monitor sèrie. Quan el microcontrolador hagi de funcionar de manera independent de l'ordinador caldrà eliminar les línies següents per evitar aquest bloqueig.

    while (!Serial) {
        ;    // Esperem que l'usuari obri el monitor sèrie
    }

El nostre programa de prova escriu unes quantes coses al monitor sèrie (instruccions Serial.print i Serial.println). Quan ja tinguem clar que el programa funciona, en podem eliminar força i probablement anirà una mica més ràpid.

 

 

 

 

 

 

 

 

 

 

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