En aquest exemple farem servir un full de càlcul de Google Drive i un script. El nostre microcontrolador llegira del full de càlcul els valors corresponents al color del NeoPixel.
Obrim la nostra pàgina de Google Drive i creem un full de càlcul nou. A la primera filera posem els títols de les columnes. A la primera, per exemple, hi podem posar Vermell i Verd i Blau a les altres dues. Serà convenient escriure uns valors a la segona filera del full per poder fer proves. Un cop introduït, es veuria d'aquesta manera:

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.
De moment, el nostre full de càlcul és només accessible per a ús personal. Si hi volem accedir des de fora ho haurem de canviar. Piquem sobre el botó Comparteix.... Se'ns obrirà una finestra en la que picarem el botó Opcions avançades. On s'indica Qui hi té accés hi deu dir Privat i ho hem de canviar per Activat: tothom que tingui l'enllaç i en el desplegable on diu Pot visualitzar haurem de triar Pot editar (en aquest cas ho podríem deixar a Pot visualitzar però llavors no podríem modificar el formulari des d'altres aplicacions). En aquesta finestra hi surt un enllaç que copiarem i ens guardarem en algun lloc, per exemple un document del bloc de notes. L'enllaç obtingut serà similar a aquest:
https://docs.google.com/spreadsheets/d/1TThsoSjkeMSfwEKy4mn_4QEYH96sxv3VURqE3WHCTswDA/edit?usp=sharing
D'aquesta adreça ens interessa el codi de la taula, que és una seqüència llarga de caràcters situada entre barres. Aquí l'hem marcada en verd perquè sigui fàcil d'identificar. Piquem al botó Fet. Ara anem a la pestanya Fitxer i piquem sobre l'opció Publica al web.... Se'ns obrirà una finestra en la que picarem el botó Publica, acceptarem la confirmació i tancarem la finestra.
En el nostre full de càlcul, anirem a la pestanya Extensions i triarem l'opció Apps 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 full de càlcul des del microcontrolador
// Oriol Boix, 2020
// 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 IdFull = "^^fvIrw5E1P4kvIYbo_QfqRWMiOSViD5PVjtWiOBxqhQ"; // Identificador del full de càlcul
// S'aconsegueix picant a Comparteix i triant Opcions avançades
var numFull = 0; // Número del full amb el que hem de treballar
var numCols = 3; // Nombre de columnes de treball que tenim al full de càlcul
var range;
// Script per interactuar amb el full de càlcul
// Funció que s'executa quan hi ha una ordre get
// La nostra funció tindrà un paràmetre:
// accio l'acció que s'ha de realitzar (darrer)
function doGet(e) {
// Assignem els paràmetres a variables
var Accio = e.parameter.accio;
// Obrim el full de càlcul
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(Accio=="darrer"){
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 + ',';
}
resultat = resultat + filera[j];
}
}
} else {
var resultat = 'Acció incorrecta';
}
return ContentService.createTextOutput(resultat);
}
També podríem seleccionar el full pel seu nom, d'aquesta manera no ens afectaria si es canvien d'ordre.
var sh = SpreadsheetApp.openById(IdFull).getSheetByName("Llistat"); // Agafem el full
range = sh.getLastRow(); // Índex de la darrera filera (+1 pels títols)
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'han requadrat aquelles línies pensades per a que es pugui personalitzar el programa. En el nostre exemple haurem de modificar la línia requadrada en vermell canviant el text entre cometes pel codi de la taula que hem trobat abans. Els elements requadrats en blau no s'han de tocar per a l'exemple que farem però sí pot ser necessari tocar-los si aprofitem el programa per a altres aplicacions.

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.
Ara que ja tenim el nostre full de càlcul preparat, anem a comentar les possibilitats que ens ofereix aquest script, per després poder-les emprar des del microcontrolador. Primer comencem per les dues variables que ens permeten personalitzar el codi. A la taula següent hi ha el seu nom i la descripció corresponent.
| Variable | Valor exemple |
Descripció |
| numFull | 0 | Número de full que fem servir, el primer és el 0 (poden haver-hi diversos fulls amb el mateix enllaç) |
| numCols | 3 | Nombre de columnes del full que fem servir (si posem 3 treballarem amb les columnes que van de la 0 a la 2) |
Els scripts es poden provar amb el navegador web. Si posem l'adreça URL a la barra d'adreces del navegador obtindrem la resposta a la pantalla. La nostra funció té un paràmetre anomenat accio que ha de ser darrer. Tal com s'ha fet, però, es podrien afegir més opcions i, fins i tot, més paràmetres en el futur. La URL hauria de quedar així:
https://script.google.com/macros/s/^^fycbysb5cirzf08AzksvdF-tQ2wc3YMK3qGQJIgfiomwcMHqI3mvE/exec?accio=darrer
en aquest cas, però, cal tenir en compte un problema afegit que ens obligarà a complicar el programa. Si fem aquesta petició des del microcontrolador, el servidor de Google ens enviarà una resposta com la següent:
HTTP/1.1 302 Moved Temporarily Content-Type: text/html; charset=UTF-8 Access-Control-Allow-Origin: * Cache-Control: no-cache, no-store, max-age=0, must-revalidate Pragma: no-cache Expires: Mon, 01 Jan 1990 00:00:00 GMT Date: Tue, 23 Apr 2019 12:27:56 GMT Location: https://script.googleusercontent.com/macros/echo?user_content_key=oenhEvjO3yPbc5l3PWi6ufvR8cirQxMahB_E8SK3bq6ntEINk5x5P5nifS8kHstSQscGgtkc5KFCdEpp6Y6gAR8RseYwh1icm5_BxDlH2jW0nuo2oDemN9CCS2h10ox_1xSncGQajx_ryfhECjZEnFqIf1F7oW1FfCOAcLCKwzzmi2u-p9u7-sO1n73TUZfMkGAo9dguy94CzIWlTYkUuAOv8ef6LK8V0WMzD7_o4OAjoDVIujcsbA&lib=Mshmn-khZ4-y7RLovKvN0gQ677b5FxXsc X-Content-Type-Options: nosniff X-Frame-Options: SAMEORIGIN X-XSS-Protection: 1; mode=block Server: GSE Alt-Svc: quic=":443"; ma=2592000; v="46,44,43,39" Accept-Ranges: none Vary: Accept-Encoding Connection: close <HTML> <HEAD> <TITLE>Moved Temporarily</TITLE> </HEAD> <BODY BGCOLOR="#FFFFFF" TEXT="#000000"> <H1>Moved Temporarily</H1> The document has moved <A HREF="https://script.googleusercontent.com/macros/echo?user_content_key=oenhEvjO3yPbc5l3PWi6ufvR8cirQxMahB_E8SK3bq6ntEINk5x5P5nifS8kHstSQscGgtkc5KFCdEpp6Y6gAR8RseYwh1icm5_BxDlH2jW0nuo2oDemN9CCS2h10ox_1xSncGQajx_ryfhECjZEnFqIf1F7oW1FfCOAcLCKwzzmi2u-p9u7-sO1n73TUZfMkGAo9dguy94CzIWlTYkUuAOv8ef6LK8V0WMzD7_o4OAjoDVIujcsbA&lib=Mshmn-khZ4-y7RLovKvN0gQ677b5FxXsc">here</A>. </BODY> </HTML>
En aquesta resposta el més important són les línies marcades en groc. El que ens diu aquesta resposta és un missatge que ens indica que el servidor ha canviat temporalment d'adreça. Aquests redireccionaments no són estranys quan es fan tasques de manteniment d'un servidor però en aquest cas no es tracta d'això. Aquesta és una de les proteccions que posa Google per evitar accions no destijades. Quan nosaltres fem una petició a l'adreça "correcta" ens envien una adreça temporal que dura molt poc temps i que ens permet fer l'acció desitjada. Només si el nostre dispositiu és capaç d'enviar immediatament la petició a la nova adreça es farà l'acció.
Aixi, doncs, hem hagut de complicar el programa respecte al dels exemples anteriors per preveure aquest canvi d'adreça i actuar en conseqüència. Malgrat tot, en algunes de les proves que hem fet el programa (o més ben dit la xarxa Wi-Fi) no ha estat prou ràpida en servir la nova connexió i en enviar la segona adreça hem rebut un nou avís de canvi, de manera que hem hagut de fer fins a tres o quatre connexions per obtenir els valors desitjats.
Ara anem a comentar el programa que hem de posar al microcontrolador. A la part de definició de variables tenim les variables server i pagina que fins ara havíem definit amb la mida que correspongués per al contingut que hi escrivíem i, en canvi, ara les definim molt més grosses perquè després necessitarem guardar-hi l'adreça que ens retorna el servidor. Com hem vist, el servidor ens retorna adreces molt llargues i, per tant, ens hem curat en salut posant valors relativament grans.
Al començament del loop és quan rebem els caràcters que envia el servidor. Hem afegit unes instruccions per detectar quan ens arriba un salt de línia (\n) i així ens assegurem que només analitzarem línies senceres. En cada connexió cerquem una línia amb un codi de resposta 200 (correcte) o 302 (redirecció temporal) i ens guardem quina hem trobat. Si era la segona, busquem també la línia que comença amb Location per agafar-ne l'adreça que hem de partir en dos trossos (just després de les dues barres) per guardar-la com a nom del servidor i com a adreça de la pàgina.
Quan la resposta ha estat el que esperàvem hem de tractar els valors rebuts però hem de tenir present que el nostre script envia només els valors de les tres columnes i no la marca de temps i, per tant, ens simplifica una mica el tractament. Finalment, fem una nova connexió passats els deu segons excepte (i això també és nou) en cas que hi hagi hagut redireccionament, que ho farem de manera immediata.
// 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 #include <Adafruit_NeoPixel.h> // biblioteca per als NeoPixels #define LED 1 // pota on hem connectat el LED #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 char server0[] = "script.google.com"; const char pagina0[] = "/macros/s/^^fycbx2VGLHQK_if-Yw9ySzbE1CZ5VPk9DYlhOQh7Z2BBumHChSs1Y/exec?accio=darrer"; char server[server_len]; char pagina[pag_len]; unsigned long darreraConnexio = 0; const unsigned long periodeConnexio = 10000UL; bool pendent, completa, redir; bool ara = false; String R, G, B; // Aquí guardarem els colors 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; Adafruit_NeoPixel cadena = Adafruit_NeoPixel(1, LED, NEO_GRB + NEO_KHZ800); WiFiSSLClient client;
void setup() { // Inicialització
Serial.begin(9600); // Monitor sèrie
cadena.begin(); // Inicialitza els NeoPixels
cadena.show();
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();
Serial.println("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
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, server_len);
pagina1.toCharArray(pagina, pag_len);
ara = true;
}
completa = false;
}
}
// Hi ha una resposta per processar
// En aquest cas, les dades estan a la darrera línia rebuda
if (pendent) {
pendent = false;
if (peticio == ""){ // Si està buit
peticio = peticioAux; // Agafem l'anterior
}
// Ara només s'envien els tres colors, no hi ha la marca de temps
R = peticio.substring(0, peticio.indexOf(","));
peticio = peticio.substring(peticio.indexOf(",") +1);
G = peticio.substring(0, peticio.indexOf(","));
B = peticio.substring(peticio.indexOf(",") +1);
cadena.setPixelColor(0, R.toInt(), G.toInt(), B.toInt());
cadena.show();
Serial.print("(");
Serial.print(R);
Serial.print(", ");
Serial.print(G);
Serial.print(", ");
Serial.print(B);
Serial.println(")");
}
// Quan toca, tornem a fer una petició
if (ara || ((millis() - darreraConnexio > periodeConnexio))) {
if (redir){
redir = false;
} else {
for (int j = 0; j < server_len; j++){
server[j] = server0[j];
}
for (int j = 0; j < pag_len; j++){
pagina[j] = pagina0[j];
}
}
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();
} else {
Serial.println("connection failed");
}
}
}
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.
Més avall podeu trobar indicacions sobre com fer una aplicació d'App Inventor que permeti modificar els valors i, per tant, el color del NeoPixel.
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.
Anem a modificar els programes anteriors per incloure aquesta variant. Primer modifiquem l'script, marcant en color les línies noves o modificades.
// Funció per interaccionar amb el full de càlcul des del microcontrolador
// Oriol Boix, 2020
// 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 IdFull = "^^fvIrw5E1P4kvIYbo_QfqRWMiOSViD5PVjtWiOBxqhQ"; // Identificador del full de càlcul
// S'aconsegueix picant a Comparteix i triant Opcions avançades
var numFull = 0; // Número del full amb el que hem de treballar
var numCols = 3; // Nombre de columnes de treball que tenim al full de càlcul
var range;
// Script per interactuar amb el full de càlcul
// Funció que s'executa quan hi ha una ordre get
// La nostra funció tindrà un paràmetre:
// accio l'acció que s'ha de realitzar (darrer)
function doGet(e) {
// Assignem els paràmetres a variables
var Accio = e.parameter.accio;
// Obrim el full de càlcul
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(Accio=="darrer"){
if (range > 1){ // Si és 1 només hi ha els títols
var filera = full[range -1]; // Agafem la darrera filera
var resultat = "color=";
for(var j = 0;j < numCols;j++){
if (j > 0){
resultat = resultat + ',';
}
resultat = resultat + filera[j];
}
}
} else {
var resultat = 'Acció incorrecta';
}
return ContentService.createTextOutput(resultat);
}
I a continuació tenim el programa modificat.
// 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 #include <Adafruit_NeoPixel.h> // biblioteca per als NeoPixels #define LED 1 // pota on hem connectat el LED #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 char server0[] = "script.google.com"; const char pagina0[] = "/macros/s/^^fycbx2VGLHQK_if-Yw9ySzbE1CZ5VPk9DYlhOQh7Z2BBumHChSs1Y/exec?accio=darrer"; char server[server_len]; char pagina[pag_len]; unsigned long darreraConnexio = 0; const unsigned long periodeConnexio = 10000UL; bool pendent, completa, redir; bool ara = false; String R, G, B; // Aquí guardarem els colors String peticio = ""; // Aquí guardarem una línia de la petició del client String peticioAux = ""; // i la petició anterior (també ho farem servir de reserva) String dades = ""; // Aquí guardarem el valor enviat per l'script int status = WL_IDLE_STATUS; Adafruit_NeoPixel cadena = Adafruit_NeoPixel(1, LED, NEO_GRB + NEO_KHZ800); WiFiSSLClient client;
void setup() { // Inicialització
Serial.begin(9600); // Monitor sèrie
cadena.begin(); // Inicialitza els NeoPixels
cadena.show();
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();
Serial.println("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
dades = "";
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 (peticioAux.startsWith("color=")){ // És la línia que conté les dades
dades = peticioAux;
}
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, server_len);
pagina1.toCharArray(pagina, pag_len);
ara = true;
}
completa = false;
}
}
// Hi ha una resposta per processar
// En aquest cas, les dades estan a la darrera línia rebuda
if (pendent) {
pendent = false;
if (dades != ""){ // Si està buit
// Primer eliminem el marcador
peticio = dades.substring(dades.indexOf("=") +1);
R = peticio.substring(0, peticio.indexOf(","));
peticio = peticio.substring(peticio.indexOf(",") +1);
G = peticio.substring(0, peticio.indexOf(","));
B = peticio.substring(peticio.indexOf(",") +1);
cadena.setPixelColor(0, R.toInt(), G.toInt(), B.toInt());
cadena.show();
Serial.print("(");
Serial.print(R);
Serial.print(", ");
Serial.print(G);
Serial.print(", ");
Serial.print(B);
Serial.println(")");
}
}
// Quan toca, tornem a fer una petició
if (ara || ((millis() - darreraConnexio > periodeConnexio))) {
if (redir){
redir = false;
} else {
for (int j = 0; j < server_len; j++){
server[j] = server0[j];
}
for (int j = 0; j < pag_len; j++){
pagina[j] = pagina0[j];
}
}
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();
} else {
Serial.println("connection failed");
}
}
}
Podríem fer una aplicació d'App Inventor que ens permeti afegir uns nous valors. Per fer-ho, caldria modificar l'script per afegir la possibilitat de rebre una comanda per a l'escriptura. A continuació es mostra una nova versió de l'script que implementa aquesta opció. L'ordre per enviar, per exemple, el color groc seria:
https://script.google.com/macros/s/^^fycbysb5cirzf08AzksvdF-tQ2wc3YMK3qGQJIgfiomwcMHqI3mvE/exec?accio=guarda&r=255&g=255&b=0
Hem marcat en color les línies que hem modificat. Hem fet que després d'escriure uns nous valors l'script ens els retorni i així podem comprovar que tot ha anat bé.
// Funció per interaccionar amb el full de càlcul des del microcontrolador
// Oriol Boix, 2020
// 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 IdFull = "^^fvIrw5E1P4kvIYbo_QfqRWMiOSViD5PVjtWiOBxqhQ"; // Identificador del full de càlcul
// S'aconsegueix picant a Comparteix i triant Opcions avançades
var numFull = 0; // Número del full amb el que hem de treballar
var numCols = 3; // Nombre de columnes de treball que tenim al full de càlcul
var range;
// Script per interactuar amb el full de càlcul
// Funció que s'executa quan hi ha una ordre get
// La nostra funció tindrà un paràmetre:
// accio l'acció que s'ha de realitzar (darrer)
function doGet(e) {
// Assignem els paràmetres a variables
var Accio = e.parameter.accio;
var R = e.parameter.r;
var G = e.parameter.g;
var B = e.parameter.b;
// Obrim el full de càlcul
var sh = SpreadsheetApp.openById(IdFull);
var sheet = sh.getSheets();
if(Accio=="guarda"){
var camps = new Array(3); // Valors per guardar a la taula
camps[0] = R;
camps[1] = G;
camps[2] = B;
var sh = SpreadsheetApp.openById(IdFull);
var sheet = sh.getSheets();
sheet[numFull].appendRow(camps); // Afegeix una fila amb la llista de dades en format matriu
Accio="darrer"; // Així retornarà els valors guardats
}
// 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(Accio=="darrer"){
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 + ',';
}
resultat = resultat + filera[j];
}
}
} else {
var resultat = 'Acció incorrecta';
}
return ContentService.createTextOutput(resultat);
}
Un cop modificat l'script, ja podem fer la nostra aplicació. L'aspecte de l'aplicació serà el següent:

Atès que en aquest web el nostre objectiu no és crear aplicacions, ens hem preocupat només del funcionament i la comprensió però no del disseny. Si ho desitgem, l'App Inventor ens permet personalitzar la disposició, els colors i altres propietats dels elements de la pantalla segons els nostres gustos.
Les següents taules mostren les propietats de cada un dels elements:
| Propietat | Valor | Comentaris |
| Nom | Label_LED | Aquest nom l'hem de posar en el requadre Components |
| Text | L'estat del LED és desconegut |
| Propietat | Valor | Comentaris |
| Nom | HorizontalArrangement_R | Aquest nom l'hem de posar en el requadre Components |
| Width | Fill parent |
| Propietat | Valor | Comentaris |
| Nom | Label_R | Aquest nom l'hem de posar en el requadre Components |
| Text | R: |
| Propietat | Valor | Comentaris |
| Nom | Slider_R | Aquest nom l'hem de posar en el requadre Components |
| ColorLeft | Pink | |
| ColorRight | Red | |
| Width | 80 % | |
| MaxValue | 255 | |
| MinValue | 0 | |
| ThumbPosition | 127 |
| Propietat | Valor | Comentaris |
| Nom | HorizontalArrangement_G | Aquest nom l'hem de posar en el requadre Components |
| Width | Fill parent |
| Propietat | Valor | Comentaris |
| Nom | Label_G | Aquest nom l'hem de posar en el requadre Components |
| Text | G: |
| Propietat | Valor | Comentaris |
| Nom | Slider_G | Aquest nom l'hem de posar en el requadre Components |
| ColorLeft | Custom: #33ff33ff | |
| ColorRight | Custom: #009900ff | |
| Width | 80 % | |
| MaxValue | 255 | |
| MinValue | 0 | |
| ThumbPosition | 127 |
| Propietat | Valor | Comentaris |
| Nom | HorizontalArrangement_B | Aquest nom l'hem de posar en el requadre Components |
| Width | Fill parent |
| Propietat | Valor | Comentaris |
| Nom | Label_B | Aquest nom l'hem de posar en el requadre Components |
| Text | B: |
| Propietat | Valor | Comentaris |
| Nom | Slider_B | Aquest nom l'hem de posar en el requadre Components |
| ColorLeft | Cyan | |
| ColorRight | Blue | |
| Width | 80 % | |
| MaxValue | 255 | |
| MinValue | 0 | |
| ThumbPosition | 127 |
| Propietat | Valor | Comentaris |
| Nom | HorizontalArrangement_enviar | Aquest nom l'hem de posar en el requadre Components |
| Width | Fill parent |
| Propietat | Valor | Comentaris |
| Nom | Button_color | Aquest nom l'hem de posar en el requadre Components |
| Text | ||
| TextColor | None |
| Propietat | Valor | Comentaris |
| Nom | Button_envia | Aquest nom l'hem de posar en el requadre Components |
| Text | Enviar |
| Propietat | Valor | Comentaris |
| Nom | Button_estat | Aquest nom l'hem de posar en el requadre Components |
| Text | Consulta estat |
| Propietat | Valor | Comentaris |
| Nom | Web_connecta | Aquest nom l'hem de posar en el requadre Components |
Atès que volem que els valors es llegeixin tant quan premem el botó com en inicialitzar la pantalla, hem creat la funció llegir i la cridem en els dos casos. Fem servir un botó per mostrar el color llegit i el color que enviarem. També hem creat una funció, que posa el color triat en el button, qque cridarem tant a l'inicialitzar la pantalla com quan es canvia la posició d'algun dels controls lliscants. El programa serà el següent:



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.