Log dei passaggi oltre la barriera laser ed email al destinatario
Allarme Laser con Arduino e Log in Spreadsheet con Google Apps Script
Nonostante non abbia alcuna base certificata di elettronica, mi piace ogni tanto dilettarmi con Arduino (prevalentemente con la parte di sviluppo degli sketch, lasciandomi invece ispirare da qualche schema elettrico già avviato per la parte hardware). Tra i vari componenti low cost che ho acquistato è presente una scheda ethernet, Fig. 1, per collegare Arduino alla rete e non avendola mai provata ho colto l'occasione per trovare un nesso con Google Apps Script.
Circa un anno fa, nel mio blog personale, ho pubblicato un articolo dal titolo "Allarme Laser con Arduino come Barriera di un Passaggio" dove si fa uso di un Laser 650nm, una fotoresistenza ed buzzer che emette un suono quando la barriera viene oltrepassata. Per lo schema, lo sketch ed il video di esempio rimando direttamente all'articolo.
La possibilità di integrare la scheda ethernet a tale progetto è nata dal tentativo di provare a scrivere su uno Spreadsheet tramite Arduino, da qui l'idea di registrare i log di quando l'allarme laser viene sollecitato.
Relativamente allo sketch non ho fatto grandi modifiche a quanto già presente nel mio codice originale se non includere la libreria EtherCard.h ed adattare il già presente esempio 'webClient' come mostrato nel codice proposto a breve. Nel link alla libreria è presente inoltre lo schema di collegamento dei pin della scheda Ethernet ad Arduino, che riporto di seguito:
ENC28J60 | Arduino Uno | Note |
---|---|---|
VCC | 3.3V | |
GND | GND | |
SCK | Pin 13 | |
MISO | Pin 12 | |
MOSI | Pin 11 | |
CS | Pin 10 | Selezionabile tramite la funzione ether.begin() |
La Fig. 2 è quanto di meglio sono riuscito a fare, si vede il laser acceso che punta alla fotoresistenza e la scheda ethernet collegata con opportuno cavo RJ45:
Prima di mostrare il codice devo premettere che ho effettuato diversi tentativi e in alcuni casi mi sono imbattuto nell'errore "HTTP/1.1 301 Moved Permanently". Ho capito sulla mia pelle che se il sito bersaglio è indicato nello sketch senza il www ma questo esiste nella forma con www e quella senza fa redirect a quest'ultima, Arduino non è in grado di seguire tale redirect e genera l'errore in questione (inserendo invece la forma con il www tutto è andato a buon fine sul sito di test che ho interrogato). Questo aspetto è molto importante quando invece si vuole interrogare uno script di Apps Script (pubblicato come applicazione web ed accessibile da chiunque), questo perché, al di là del protocollo https anziché http, nella documentazione ufficiale viene espressamente detto che "Per motivi di sicurezza, il contenuto restituito dal servizio Content non viene pubblicato da script.google.com ma è invece reindirizzato ad un URL one-time con dominio script.googleusercontent.com" (https://developers.google.com/apps-script/guides/content).
Per tale motivo ho dovuto utilizzare un servizio gratuito di terze parti, nel caso specifico chiamato PushingBox, che tra le altre cose permette di interrogare un URL esterno passandogli dei parametri.
Ho quindi creato un nuovo Spreadsheet e nel servizio esterno configurato il suo URL ed il tipo di chiamata, Fig. 3:
Successivamente ho definito lo scenario, ossia i parametri dinamici da passare (nel caso specifico la zona dove l'allarme è scattato; nel mio prototipo ne esiste una sola ma a scopo esemplificativo è mostrato il passaggio dei valori da Arduino ad Apps Script), Fig. 4:
Il codice dello sketch da caricare su Arduino è il seguente (nota: rispetto allo schema dell'articolo indicato all'inizio, il pin 10 riferito al led rosso è stato spostato sul pin 8 per lasciare libero il pin 10 al connettore CS della scheda ethernet che lo prevede di default, nonostante sia comunque configurabile). Nella chiamata al webservice è definito anche un parametro devid (DeviceID) che è un identificativo univoco fornito dal servizio al momento della creazione dello scenario:
#include <EtherCard.h>
// ethernet interface mac address, must be unique on the LAN
static byte mymac[] = { 0x74,0x69,0x69,0x2D,0x30,0x31 };
byte Ethernet::buffer[700];
static uint32_t timer;
const char website[] PROGMEM = "api.pushingbox.com";
// called when the client request is complete
static void my_callback (byte status, word off, word len) {
Serial.println(">>>");
Ethernet::buffer[off+300] = 0;
Serial.print((const char*) Ethernet::buffer + off);
Serial.println();
Serial.println("chiamata al webservice effettuata");
}
const int led_rosso = 8; // nello script del laser era 10 ma lo slot è occupato dal pin per la scheda ethernet
const int led_verde = 9;
const int pin_input_bottone = 7;
const int sensorLaser = 5;
const int buzzerPin = 3;
const int debounceDelay = 200;
const int intervallo_base = 250;
int pin_input_bottone_valore=0;
int bottone_stato_pressione = 0;
int luceQty;
unsigned long currentMillis = 0;
unsigned long previousMillis = 0;
String bln_reset_mail_sent = "0";
void setup () {
pinMode(led_verde, OUTPUT);
pinMode(led_rosso, OUTPUT);
pinMode(pin_input_bottone, INPUT);
pinMode(sensorLaser, OUTPUT);
pinMode(A0,INPUT);
Serial.begin(57600);
Serial.println(F("
[webClient]"));
if (ether.begin(sizeof Ethernet::buffer, mymac) == 0)
Serial.println(F("Failed to access Ethernet controller"));
if (!ether.dhcpSetup())
Serial.println(F("DHCP failed"));
ether.printIp("IP: ", ether.myip);
ether.printIp("GW: ", ether.gwip);
ether.printIp("DNS: ", ether.dnsip);
#if 1
// use DNS to resolve the website's IP address
if (!ether.dnsLookup(website))
Serial.println("DNS failed");
#elif 2
// if website is a string containing an IP address instead of a domain name,
// then use it directly. Note: the string can not be in PROGMEM.
char websiteIP[] = "192.168.1.1";
ether.parseIp(ether.hisip, websiteIP);
#else
// or provide a numeric IP address instead of a string
byte hisip[] = { 192,168,1,1 };
ether.copyIp(ether.hisip, hisip);
#endif
ether.printIp("SRV: ", ether.hisip);
}
void loop () {
ether.packetLoop(ether.packetReceive());
currentMillis = millis();
pin_input_bottone_valore = digitalRead(pin_input_bottone);
if (pin_input_bottone_valore == HIGH) {
switch (bottone_stato_pressione) {
case 0:
bottone_stato_pressione = 1;
break;
case 1:
bottone_stato_pressione = 0;
break;
//default:
// non fare niente
}
delay(debounceDelay);
}
if (bottone_stato_pressione == HIGH) {
if ((currentMillis - previousMillis) >= (intervallo_base)) {
digitalWrite(led_rosso, LOW);
digitalWrite(led_verde, HIGH);
digitalWrite(sensorLaser, 1);
luceQty = analogRead(A0);
if (luceQty<100) {
tone(buzzerPin, 500, 1000);
if (bln_reset_mail_sent == "0") {
Serial.println();
Serial.print("<<< REQ ");
ether.browseUrl(PSTR("/pushingbox?devid=v5082EBEXXXXXXXX&zona="), "Porta%20di%20ingresso", website, my_callback);
bln_reset_mail_sent = "1";
}
} else {
bln_reset_mail_sent = "0";
noTone(buzzerPin);
}
updateMillis();
}
} else {
if ((currentMillis - previousMillis) >= (intervallo_base)) {
noTone(buzzerPin);
digitalWrite(led_verde, LOW);
digitalWrite(led_rosso, HIGH);
digitalWrite(sensorLaser, 0);
updateMillis();
}
}
}
void updateMillis() {
previousMillis = currentMillis;
}
Lo script in Google Apps Script è invece di tipo standalone (nel quale è presente l'identificativo dello Spreadsheet su cui scrivere) e dovrà essere distribuito come applicazione web eseguita come il creatore stesso dello script ma con accesso a chiunque compresi utenti anonimi:
// Lo script viene interrogato dal servizio pushingbox.com tramite l'API api.pushingbox.com
function doGet(e) {
var result = '';
var bln_sendMail = false;
if (e.parameter == undefined) {
result = 'Nessun parametro rilevato';
} else {
var ss_id = '1S0Wq7rHB6OYo-WgktB...'; // Id dello Spreadsheet
var rowData = [];
rowData[0] = Utilities.formatDate(new Date(), "Europe/Rome", "YYYY/MM/dd HH:mm:ss");
for (var param in e.parameter) {
var value = stripQuotes(e.parameter[param]);
switch (param) {
case 'zona': // nome del parametro: 'zona'
rowData[1] = value; // valore del parametro
bln_sendMail = true;
result = 'Parametro rilevato';
break;
default:
result = "Parametro non previsto";
}
}
if (bln_sendMail) {
var ss_sh = SpreadsheetApp.openById(ss_id).getSheets()[0]; // Foglio 1
var newRow = ss_sh.getLastRow() + 1;
var newRange = ss_sh.getRange(newRow, 1, 1, rowData.length);
newRange.setValues([rowData]);
MailApp.sendEmail("miamail@gmail.com", "Allarme " + rowData[1] + ": " + rowData[0], "Suonato allarme nella zona " + rowData[1] + " il " + rowData[0]);
}
}
return ContentService.createTextOutput(result);
}
// Rimuove eventuali apici, doppi e singoli, dall'inizio e della fine della stringa passata
function stripQuotes(str) {
return str.replace(/^["']|['"]$/g, "");
}
È possibile osservare lo script in esecuzione tramite il log della porta seriale di cui riporto di seguito uno screenshot nel quale sono evidenziate due chiamate al webservice:
Il funzionamento è il seguente: una volta che la barriera laser viene interrotta, una chiamata verrà lanciata da Arduino verso il servizio di terze parti che a sua volta interroga l'URL dello Script Web creato con Apps Script passando l'opportuno parametro "zona". Lo script esegue di default la funzione doGet(e) e se riconosce il parametro passato scrive la data, l'ora e la zona all'interno dello Spreadsheet definito, Fig. 6:
Lo script contestualmente invia un'email con le stesse informazioni all'indirizzo specificato, Fig. 7:
Questo breve video rappresenta una dimostrazione del progetto in funzione:
Lo script, così come il sistema hardware, può essere ampliato con altri laser ipotizzando un uso interno ad un'abitazione e quindi oltre alla porta di ingresso mettere in sicurezza finestre ed altri accessi.
L'approccio per scrivere da Arduino ad uno Spreadsheet può inoltre essere esportato in qualsiasi altro progetto che necessiti di registrare dati tra questi due diversi sistemi.
Non ci sono commenti
Nessuno ha ancora commentato questo articolo, fallo tu per primo!
scrivi un commento