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ó.

Despleguem l'opció Afegeix un calendari i piquem a 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.

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.

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.

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:

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 = "Resposta" + salt; // La paraula resposta ens marcarà l'inici de les dades rebudes
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.

Un cop personalitzat el programa l'hem de guardar, picant el botó que es mostra a continuació.
Picarem el botó Implementar.
En el desplegable triarem Nueva implementación i s'obrirà una finestra similar a la següent:

El primer cop que ho fem, haurem de picar en el botó que es mostra a continuació.
I triar l'opció Aplicación web. La finestra ens preguntarà en nom de qui volem que s'executi l'aplicació (li direm Yo) i qui hi té accés (li direm que qualsevol usuari). Finalment, picarem el botó Implementar.

Se'ns mostrarà una pantalla en la que se'ns indicarà l'adreça URL de l'aplicació, que haurem de copiar.
| URL script | https://script.google.com/macros/s/^^fycbxqrJpVA-KT1sUd8HIta643R3bH4ixpDahttayGSGjkpHUBjPQ/exec |
Un cop estiguem, podem picar el botó Listo.
Atenció: Hem de recordar que cal guardar el programa (botó del disquet) abans d'implementar, si no ho fem ens implementarà la darrera versió guardada que no serà l'actual.
Important: ës probable que quan implementem l'script ens proporcioni una nova adreça diferent de l'anterior. Si és així, l'adreça antiga seguirà funcionant però executarà la versió anterior de l'script. És necessari, doncs, actualitzar l'adreça del programa cada cop que el modifiquem.
Els scripts es poden provar amb el navegador web. Si tenim un calendari amb les següents activitats

i posem l'adreça URL a la barra d'adreces del navegador, aquest script ens tornarà la següent resposta:
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 és la que ens indica l'inici de la resposta, la segona correspon al primer esdeveniment i la tercer 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. 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("Resposta"))){
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() > 3){ // 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"));
}
}
}
El programa anterior dona per suposat que les dades que envia l'script arriben en una de les dues darreres línies rebudes. Però podria passar que no fos així i, de fet, sovint Google modifica el funcionament de les seves aplicacions per introduir elements de seguretat. Podríem fer que l'script enviés les dades amb un marcador, de manera que la línia on hi ha les dades quedés perfectament identificada. Podeu trobar una mostra de com fer-ho en aquest exemple.
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.
En aquest altre espai web pots trobar informació sobre els scripts.

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