Aquest exemple és una modificació d'un exemple anterior en el que en lloc de llegir només un estat (corresponent al LED) llegirem els tres valors corresponents a les components de color per al NeoPixel.
En el full de càlcul posarem tres columnes, una per al vermell, una per al verd i una per al blau.
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à 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;
// Llegim les dades del full
var full = sheet[0].getDataRange().getValues();
range = sheet[0].getLastRow(); // Índex de la darrera filera (+1 pels títols)
if(Accio=="darrer"){
var resultat = "";
if (range > 1){ // Si és 1 només hi ha els títols
var filera = full[range -1]; // Agafem la darrera filera
resultat = "RGB " + filera[0] + "," + filera[1] + "," + filera[2];
}
} else {
var resultat = 'Acció incorrecta';
}
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 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 el color actual del NeoPixel\n";
llista = llista + "/color - Envia un nou color\n";
llista = llista + " Format: /color@R,G,B\n";
llista = llista + " Exemple: /color@255,127,63\n";
sendText(id,llista);
fet = 1;
}
if (cmd == '/color'){
resposta = 'Falten paràmetres o són incorrectes';
if (comanda.length > 1){
var par = comanda[1]; // Ho passem a minúscules
var compon = par.split(","); // Separa per les comes
if (compon.length > 2){
var camps = new Array(3); // Valors per guardar a la taula
camps[0] = compon[0];
camps[1] = compon[1];
camps[2] = compon[2];
sheet[0].appendRow(camps); // Afegeix una fila amb la llista de dades en format matriu al primer full
resposta = 'Valors guardats';
}
}
sendText(id,resposta);
fet = 1;
}
if (cmd == '/llegir'){
var full = sheet[0].getDataRange().getValues();
var rang = sheet[0].getLastRow(); // Índex de la darrera filera
if (rang > 1){ // Si és 1 només hi ha els títols
var filera = full[rang -1]; // Agafem la darrera filera
resposta = "(" + filera[0] + "," + filera[1] + "," + filera[2] + ")";
} 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 tres
var rangeVal = sheet[0].getRange(range, 1, 1, 3); // 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 <Adafruit_NeoPixel.h> // biblioteca per als NeoPixels #define LED 1 // Sortida que correspon al NeoPixel #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/AKfycbx2VGLHQK_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 resposta = ""; 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
//Serial.write(c);
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.indexOf("RGB") >= 0){ // Dades esperades
resposta = 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;
Serial.print("Rebut: ");
Serial.println(resposta);
// Primer suprimim "RGB " i després separem els tres colors
resposta = resposta.substring(resposta.indexOf(" ") +1);
R = resposta.substring(0, resposta.indexOf(","));
resposta = resposta.substring(resposta.indexOf(",") +1);
G = resposta.substring(0, resposta.indexOf(","));
B = resposta.substring(resposta.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(")");
resposta = "";
}
// 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");
}
}
}
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.