Aquest exemple és una modificació d'un exemple anterior en el que en lloc de llegir l'estat del LED escriurem al full de càlcul els dos valors corresponents a les lectures de temperatura i humitat. Per un altre costat, podrem llegir els darrers valors guardats des de Telegram.
En el full de càlcul posarem dues columnes, una per a la temperatura i una per a la humitat.

Les pàgines i els serveis de Google canvien d'aspecte amb certa freqüència. Les imatges que trobarem en aquest apartat, per tant, poden no correspondre exactament amb les reals. Aquí l'important són els passos, no l'aspecte.
Aquest és l'script que farem servir:
// Funció per interaccionar amb el full de càlcul des de Telegram
// Oriol Boix, 2020 Basat en un exemple de Ferran Mas
// 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
var token = "^^34368844:AAFIpk-e7j3UZtMQYQaTduf4hEhnDqIcNXI"; // API Token de Telegram
var telegramUrl = "https://api.telegram.org/bot" + token; // Url que comunica el nostre xatbot amb Telegram
// Adreça de l'script (ens la donen en el moment de publicar-lo)
var webAppUrl = "https://script.google.com/macros/s/AKfycbzXSTtXkzqyJ_sABbGZanVX2EuRAIUB5vct53WXp6-MbLscpLHO/exec";
// Id del full de càlcul (l'obtenim quan el compartim)
var IdFull = "15CWPEcIVpS512vGw3ziCZHEuMev838GAaQ6yJKnS2Pg";
// Funció que envia un text a Telegram
function sendText(chatId,text_env){
var cont = {
method: "post",
payload: {
method: "sendMessage",
chat_id: String(chatId),
text: text_env,
parse_mode: "HTML"
}
}
UrlFetchApp.fetch(telegramUrl + '/', cont);
}
var sh = SpreadsheetApp.openById(IdFull); // Obrim el full de càlcul
var sheet = sh.getSheets(); // Agafem els fulls
// Funció que s'executa quan hi ha una ordre get
// La nostra funció tindrà dos paràmetres:
// t Valor de la temperatura
// h Valor de la humitat
function doGet(e) {
var resultat = '';
var camps = new Array(2); // Valors per guardar a la taula
// Assignem els paràmetres a variables
var Temp = e.parameter.t;
var Hum = e.parameter.h;
if ((Temp == undefined) || (Hum == undefined)){
resultat = 'Falten paràmetres';
} else {
camps[0] = Temp;
camps[1] = Hum;
sheet[0].appendRow(camps); // Afegeix una fila amb la llista de dades en format matriu
resultat = 'Valors guardats';
}
return ContentService.createTextOutput(resultat);
}
// Funció que s'executa quan l'script rep una ordre post
function doPost(e){ // Funció que rep les dades enviades per Telegram
var temp = 200;
var humi = 200;
var full = sheet[0].getDataRange().getValues();
range = sheet[0].getLastRow(); // Índex de la darrera filera
if (range > 1){ // Si és 1 només hi ha els títols
var filera = full[range -1]; // Agafem la darrera filera
temp = filera[0];
humi = filera[1];
}
var fet = 0;
var resposta = '';
var dades = JSON.parse(e.postData.contents); // Tracta les dades rebudes com a JSON i les guarda a dades
// Extraiem les dades
var text = dades.message.text; // Recupera el text del missatge
var id = dades.message.chat.id; // Recupera l'identificador de la finestra des de la que s'ha enviat
var id_usuari = dades.message.from.id; // Recupera l'identificador de l'usuari que ha escrit el missatge
var id_missatge = dades.message.message_id; // Recupera l'identificador del missatge
var lang = dades.message.from.language_code ; // Recupera la llengua que l'usuari té configurada a Telegram
var nom_usuari = dades.message.from.first_name ; // Recupera el nom de l'usuari que ha enviat el missatge
var posicio = dades.message.location; // Recupera la localizació de l'usuari, si és possible
// La comanda pot tenir paràmetres, separats amb @
// Descomposem el text rebut a partir de les @
var comanda = text.split("@"); // Separa per les @
var cmd = comanda[0]; // Ho passem a minúscules
if (cmd == '/llistat'){
var llista = "/llistat - Llista de les comandes disponibles\n"; // Text que es retornarà a Telegram
llista = llista + "/llegir - Consulta les dades de temperatura i humitat\n";
llista = llista + "/temp - Consulta les dades de temperatura\n";
llista = llista + "/humi - Consulta les dades d'humitat\n";
sendText(id,llista);
fet = 1;
}
if ((cmd == '/llegir') || (cmd == '/temp') || (cmd == '/humi')){
if ((temp < 200) && (humi < 200)){ // Si és 200 no hi ha valors
if (cmd == '/llegir'){
resposta = temp + " °C\n" + humi + " %\n";
}
if (cmd == '/temp'){
resposta = temp + " °C\n";
}
if (cmd == '/humi'){
resposta = humi + " %\n";
}
} else {
resposta = "No hi ha cap dada guardada";
}
sendText(id,resposta);
fet = 1;
}
// Si no hem entrat a cap if vol dir que la comanda no s'ha reconegut
if (fet == 0){
resposta = "Comanda incorrecta";
sendText(id,resposta);
}
}
En el nostre script afegim una línia al full de càlcul cada cop que arriben dades. Podríem fer que sempre s'escrivissin a la mateixa filera si canviem la línia marcada en color per les següents:
var valu = new Array(1);
valu[0] = camps; // Converteix el vector en una matriu d'una filera i una columnes
var range = sheet[0].getLastRow();
if (range > 1){ // Mira si ja hi ha un valor
// La funció getRange ens permet seleccionar el grup de caselles sobre les que anem a treballar,
// en aquest cas a escriure
// El primer paràmetre és la filera on comença el grup, en el nostre cas la darrera escrita
// El segon paràmetre és la columna on comença el grup, en el nostre exemple la primera
// El tercer paràmetre és el nombre de fileres que tindrà el grup, per a nosaltres una
// El quart paràmetre és el nombre de columnes que ocupa el grup, en el nostre cas dues
var rangeVal = sheet[0].getRange(range, 1, 1, 2); // Selecciona una casella
rangeVal.setValues(valu); // Guarda els valors a les caselles, substituïnt els anteriors
} else {
// Si no n'hi ha cap, afegeix una fila amb la llista de dades en format matriu
sheet[0].appendRow(camps);
}
I aquest serà el programa del microcontrolador:
#include <SPI.h> // Carreguem la biblioteca SPI #include <WiFiNINA.h> // Carreguem la biblioteca WiFiNINA #include "DHT.h" #define DHTPIN 2 #define DHTTYPE DHT22 #define server_len 50 #define pag_len 400
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"; const String pagina_base = "/macros/s/AKfycbwzJfJIZd9-yS6prAbLRDNHUTFT4Ua122Kv2yb-jo3jd73Gl9x0/exec"; String pagina0 = ""; char server[server_len]; char pagina[pag_len]; unsigned long darreraConnexio = 0; const unsigned long periodeConnexio = 10000UL; bool pendent, completa, redir; bool ara = false; float hum, temp; 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);
void setup() { // Inicialització
Serial.begin(9600); // Monitor sèrie
dht.begin();
while (!Serial) {
; // Esperem que l'usuari obri el monitor sèrie
}
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);
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;
}
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);
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();
data = "?";
data += "t=";
data += temp;
data += "&h=";
data += hum;
if (redir){
redir = false;
} else {
pagina0 = pagina_base + data;
server0.toCharArray(server, server_len);
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 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.
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, les podem eliminar quasi totes 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.