Un sistema per la verifica delle prenotazioni alla WebApp della mensa di Poggiolevante.
Questo progetto è open source: chiunque può scaricare i file necessari, ricreare il progetto e contribuire al suo miglioramento. Non ci sono restrizioni di licenza d'uso, ma si invita a citare che è stato realizzato dagli studenti ASIRID.
Tutto il materiale necessario si trova su GitLab.
Il progetto è nato dall'esigenza del nostro direttore di sapere, in maniera immediata, chi dei prenotati non si è presentato a mensa agli orari prestabiliti (sia a pranzo che a cena).
Il sistema dovrà comunicare con l'applicazione di prenotazione dei pasti già esistente (APPetito by Poggiolevante), per poter poi verificare a fine pasto chi tra le persone prenotate (coloro che hanno la presenza) sono realmente presenti (e quindi hanno prelevato il tovagliolo), e chi non si è presentato ai pasti e ha la "presenza", avrà il nome segnalato con la casella che diventerà di colore giallo (sull'interfaccia web per la visualizzazione delle prenotazioni dei residenti).
Qui un esempio:
Inoltre il sistema dovrà accendersi e spegnersi a determinati orari, facendo riferimento a parametri specificati su un file di configurazione.
Per risolvere questo problema, abbiamo pensato alla costruzione di una sorta di struttura ad "Alveare" in cui:
- ogni cella è associata ad un nome di stanza (e quindi associata ad un residente).
- ogni cella è dotata di una fotoresistenza illuminata da un LED.
- ogni cella conterrà il tovagliolo del residente.
Tutto il circuito della struttura è pilotato da un ESP8266 Wemos D1 e i valori delle fotoresistenze sono catturati degli EXPANDER IO, nel nostro caso abbiamo utilizzato gli expander SX1509.
Grazie a quest'ultimi e in particolare al modulo WiFi del Wemos D1 è possibile inviare i valori delle fotoresistenze ad un url che aggiornerà il DB delle stanze dei residenti.
Per quanto riguarda l'accensione e lo spegnimento di tutto il sistema, l'ESP8266 fungerà anche da WebServer e chiamando url specifici sarà possibile attivare o disattivare i LED. Le chiamate a questi url saranno effettuate in maniera automatizzata da un dispositivo esterno (nel nostro caso da un RaspberryPi 3B+).
!ATTENZIONE Il processo di costruzione della struttura (senza pannelli posteriori con circuito) non è descritto in questa documentazione. Nel nostro caso è stata commissionata a terze parti e sono stati richiesti 50 celle divise in due parti. Tutta la struttura è stata costruita in legno.
Gli expander I/O sono settati in pull-up, di conseguenza ogni pin I/O (settato in input) sarà ad alto livello logico (Vcc | 1 | Valore Alto) quando la luce incidente è al di sotto della soglia, e passerà a basso livello logico (GND | 0 | Valore Basso) quando la luce incidente supera la soglia e la fotoresistenza fornisce una bassa resistenza.
Ricordando che:
- Il GL5516 è un sensore di luce a resistenza variabile, noto anche come fotoresistore o LDR (Light Dependent Resistor). La sua resistenza diminuisce al diminuire dell'intensità luminosa a cui è esposto.
- Ad ogni GL5516 corrisponde un pin I/O sull'expander, nel nostro caso gli expander sono 3 per un totale di 48 pin I/O. Invece le fotoresistenze sono 40, in quando 40 sono le stanze presenti in collegio.
- Tensione soglia della fotoresistenza GL5516: 0.7V.
Quindi se il tovagliolo copre la fotoresistenza il valore rilevato sarà 0 altrimenti sarà 1.
!IMPORTANTE il LED deve puntare verso la fotoresistenza e la fotoresistenza deve essere rivolta verso il LED, perchè altrimenti vi potrebbero essere errori di rilevazione.
Ogni volta che l'ESP8266 leggerà i valori delle fotoresistenze (le letture vengono fatte ogni 3 secondi) effettuerà una GET ad un url passando tramite query string il valore decimale di ogni expander I/O.
La lettura da ogni expander restituirà una sequenza di bit, nello specifico 16 bit, che saranno convertiti dall'ESP8266 in decimale.
Sarà il back-end ad occuparsi di aggiornare il DB delle stanze con i relativi valori riconvertendo in binario i valori decimali ricevuti.
Per visualizzare poi chi ha preso o meno il tovagliolo, sarà necessario chiamare un'altro URL che restituirà la tabella con i relativi colori. Questa operazione verrà effettuata da un'altro dispositivo, nel nostro caso da un RaspberryPi che mostrerà la tabella su un monitor.
Riga Gialla -> per chi non ha preso il tovagliolo ed è prenotato (per pranzo o cena). Riga Bianca -> per chi ha preso il tovaglio ed è prenotato (per pranzo o cena) e per chi invece non è prenotato.
Come già descritto in precedenza l'accensione e lo spegnimento dei LED dell'intero sistema sarà gestita dallo stesso RaspberryPi che gestisce la visualizzazione, sempre tramite chiamtate a URL specifiche.
Le componenti fanno riferimento al sistema da noi utilizzato.
- ESP8266 Wemos D1
- 40 fotoresistenze GL5516
- 40 LED bianchi
- 40 Resistenze da 330Ohm
- 40 Resistenze da 2.7kOhm
- 40 Rettangoli di legno (andranno incisi per inserire la fotoresistenza e i cavi di rame che permetteranno il collegamento con il circuito dei panneli posteriori)
- 3 Expander I/O SX1509
- 1 Relay (per gestire accensione e spegnimento dei LED)
- 1 Regolatore di tensione STEP-DOWN (per l'alimentazione delle fotoresistenze)
- 1 Alimentatore da 5V
- 1 PCB (per collegare gli SX1509, ESP8266 e pin per attivazione del Relay)
- Header Femmina (da saldare sul PCB e sugli Expander)
- Header Maschio (da saldare sugli Expander)
- Cavi Jumper Maschio-Femmina o Maschio-Maschio (dipende da come si effettuano le saldature) (per il collegamento tra fotoresistenze e expander)
- 80 piastre di contatto a molla (da saldare sul circuito dei panneli posteriori)
- 80 piastre di contatto (da saldare sugli slot di legno, per collegare slot con circuito dei pannelli)
Come questi:
- Cavi di rame (per i vari collegamenti)
- Molta colla a caldo (per isolare alcuni collegamenti e per fissare il circuito ai pannelli posteriori della struttura)
- Olio di Gomito
Opzionali
- Terminali WAGO (per un migliore cable management)
- Ulteriore utilizzo di 40 piastre di contatto a molla (in questo caso da incollare dietro alle fascette di legno utilizzate per i nomi delle stanze, serviranno per fare pressione sugli slot di legno in modo tale che assicurino il contatto tra le piastre di contatto a molla del circuito del pannelo posteriore e lo slot stesso)
Di seguito sono riportate le immagini delle topologia del circuito PCB, Pannelli Posteriori e del singolo slot.
Ecco quello da noi costruito:
Ecco lo slot che occupa i vari collegamenti:
Ecco un esempio dei nostri:
Qui invece una foto da vicino dei rettangoli di legno rimovibili con fotoresistenza:
Una volta effettuati tutti i collegamenti e saldature del caso, si consiglia di raggruppare i cavi per il segnale delle fotoresistenze in gruppi da 4 (questo almeno è quello che abbiamo fatto noi, utilizzando dello scotch). Ad ogni colonna corrisponde una quaterna di cavi.
Ecco un esempio:
Osservazione: Nell'immagine sopra riportata si utilizza una versione alpha del PCB, ma comunque funzionante, rispetto a quello precedentemente mostrato.
Una volta raggruppati i cavi in quaterne si consiglia di etichettarli e di segnare con una matita quale dei pin fa riferimento al primo slot (dal basso verso l'alto). Noi abbiamo assegnato le lettere dell'alfabeto ad ogni colonna della struttura di legno, quindi A B C D E F G H I L (da sinistra verso destra rispetto alla struttura).
Successivamente abbiamo collegato le quaterne di cavi al PCB in questa maniera:
- Python3 (da utilizzare sul dispositivo esterno per l'accensione e spegnimento dei LED)
- Linux(Debian) (per il dispositivo esterno, in quanto per automatizzare accensione e spegnimento utilizzeremo crontab)
- Modulo 'requests' per Python3 (servirà per la chiamata agli URL per il reset ESP8266, accensione e spegnimento LED); per installarlo:
- Aprire il terminale e digitare:
pip3 install requests
- GIT
- Repository GitLab: {tovaglioli-intelligenti}
- Scegliere un percorso (es. sul Desktop)
- Aprire il terminale (windows o linux a seconda del dispositivo che si sta utilizzando) e digitare:
git clone https://gitlab.com/poggiolevante/tovaglioli-intelligenti
- Arduino IDE (per la programmazione dell'ESP8266)
- Un WebServer con PHP (nel nostro caso Appetito)
La Repository è cosi composta:
- /back_end/: contiene tutti i file .php da caricare nel WebServer.
- /Docs/: contiene tutti i file .drawio utilizzati per questa documentazione, tutte le immagini di questa documentazione (/img/) e un file .pdf che invece è la documentazione dell'SX1509.
- /esp_code/: contiene il progetto Arduino IDE (/PORTATOVAGLIOLI) da caricare sull'ESP8266.
- /py_code_LED/: contiene i file .py da caricare e automatizzare con il dispositivo esterno. (per reset dispositivo, accensione e spegnimento dei LED)
- README.md: questa documentazione.
- Posizionare nella cartella desiderata (ovviamente nella root del WebServer) i file in ./back_end
Osservazione: il codice da noi caricato nella repository riguardo al back_end della WebApp per gestire l'aggiornamento del DB e la visualizzazione della tabella dei residenti è strettamente specifico al nostro caso d'uso, per questo non spiegheremo l'intero processo di implementazione. Tuttavia di seguito vengono riportati le porzioni di codice più importanti.
- Modificare il codice di setting.php
<?php
//connessione con il db
$link = mysqli_connect("<server_ip_address>","<db_user>","<db_psw>") or die ("Impossibile connettersi al server"); // <server_ip_address> -> ip del DB || <db_user> -> nome utente || <db_psw> -> password per la connesione al DB
$con_db = mysqli_select_db($link,"<db_name>") or die ("Impossibile aprire il db"); // <db_name> -> nome del DB da aggiornare
$file_config = file("../config.txt");
$config = array();
foreach($file_config as $line){
$configArray = explode("=",$line);
$config[$configArray[0]] = trim($configArray[1]);
}
$config["feste"] = explode(",", $config["feste"]);
?>
- Posizionare il file di configurazione nella cartella desiderata e modificarlo come più si preferisce (chiamare il file "config.txt")
CenaFeriale= 20:30
CenaFestivo= 20:00 // Orari di accensione dei LED
PranzoFeriale= 14:00
PranzoFestivo= 13:30
PranzoSabato= 13:30
CenaSabato= 20:30
DurataPasto= 30 // Dopo quanto, dall'inizio dei pasti si dovranno spegnere i LED (minuti)
AccensioneLED= 5 // Quanto prima si dovranno accendere i LED (minuti) (in questo caso 5 minuti prima)
feste= 2022-11-01,2022-12-08,2022-12-25,2022-12-26,2023-01-06,2023-04-09,2023-04-10,2023-04-25,2023-05-01,2023-05-08,2023-06-02,2023-08-15,2023-11-01,2023-12-08,2023-12-25,2023-12-26
// Giorni considerati Festivi
- Codice sensore.php:
<html>
<body>
<?php
#ini_set('display_errors', 1);
#ini_set('display_startup_errors', 1); // Per la visualizzazione di eventuali errori php decommentare
#error_reporting(E_ALL);
include('./setting.php'); // Richiamare questa riga ogni qualvolta si voglia utilizzare il DB delle stanze
if(isset($_GET['data1']) && isset($_GET['data2']) && isset($_GET['data3']) && isset($_GET['map1']) && isset($_GET['map2']) && isset($_GET['map3'])) {
$arr = array();
$array_stanza = array();
$struttura = array("Adelfia-Canneto", "Adelfia-Montrone", "Alberobello", "Bari", "Barletta", "Basilicata", "Bitetto", "Calabria", "Castel del Monte", "Fasano",
"Foggia", "Gioia del Colle", "Giovinazzo", "Isole Tremiti", "Lecce", "Locorotondo", "Manfredonia", "Massafra", "Mesagne", "Molfetta",
"Molise", "Monopoli", "Monte Sant\'Angelo", "Oria", "Ostuni", "Peschici", "Polignano", "Puglia", "Rodi Garganico", "Rutigliano", // Array contenente nomi delle stanze
"Ruvo", "Sacerdote", "San Marco in Lamis", "Santo Spirito", "Serracapriola", "Siponto", "Squinzano", "Taranto", "Trani", "Valle d\'Itria");
$hourMin = date('H:i');
$today = date('Y-m-d');
$data1 = $_GET['data1'];
$data2 = $_GET['data2'];
$data3 = $_GET['data3'];
$esp1_map = explode(",", $_GET["map1"]);
$esp2_map = explode(",", $_GET["map2"]);
$esp3_map = explode(",", $_GET["map3"]); // Manipolazione dei dati passati tramite queryString
$ext1 = dec_bin($data1);
$ext2 = dec_bin($data2); // Conversione in binario dei valori decimali ricevuti
$ext3 = dec_bin($data3);
$arr1 = str_split($ext1);
$arr2 = str_split($ext2);
$arr2 = array_slice($arr2, 8, 8);
$arr3 = str_split($ext3);
$mappa = re_matrixv2($arr1, $arr2, $arr3, $esp1_map, $esp2_map, $esp3_map);
$arr_mappa = mat_to_vect($mappa, 4, 10);
print_matrix($mappa, 4, 10);
echo "<br><br>". implode(", ", $arr_mappa) . "<br>";
for($i = 0; $i < sizeof($struttura); $i++){
// Update dei valori del DB delle stanze
$insert_tovagliolo = "UPDATE `tovaglioli`
SET valore = '$arr_mappa[$i]', data = '$today', ora = '$hourMin'
WHERE stanza = '$struttura[$i]'";
$result = mysqli_query($link, $insert_tovagliolo);
}
} else {
echo "!!Error!! Some variable are not set :(";
}
function dec_bin($num) {
$b = "";
for($i = 0; $i < 16; $i++){
$b .= $num % 2;
$num = $num / 2;
}
return strrev($b);
}
function re_matrix($v1, $v2, $v3){
$struct = array(
array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
);
$dem_f = 15;
for($i = 0; $i < 10; $i++){
for($j = 0; $j < 4; $j++){
if($i >= 0 and $i < 4){ $struct[$j][$i] = $v3[$dem_f - $j]; }
else if($i == 4){ $struct[$j][$i] = $v2[15 - $j]; }
else if($i == 9){ $struct[$j][$i] = $v2[7 - $j]; }
else{ $struct[$j][$i] = $v1[$dem_f - $j]; }
}
if ($dem_f == 3){ $dem_f = 15; }
else if (($i >= 0 and $i < 4) or ($i >= 5 and $i < 9)) { $dem_f = $dem_f - 4; }
}
return $struct;
}
function re_matrixv2($v1, $v2, $v3, $map1, $map2, $map3){
$struct = array(
array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
);
for($i = 0; $i < 16; $i++){
$row = $i % 4;
$col = $map1[$i / 4] - 1;
$struct[$row][$col] = $v1[15 - $i];
if($i >= 0 and $i <= 7){
$col = $map2[$i / 4] - 1;
$struct[$row][$col] = $v2[7 - $i];
}
$col = $map3[$i / 4] - 1;
$struct[$row][$col] = $v3[15 - $i];
}
return $struct;
}
function mat_to_vect($mat, $r, $c){
$vet = array();
for($i = 0; $i < $r; $i++){
for($j = 0; $j < $c; $j++){
array_push($vet, $mat[$i][$j]);
}
}
return $vet;
}
function print_matrix($mat, $r, $c){
echo "</br>\n";
for($i = 0; $i < $r; $i++){
for($j = 0; $j < $c; $j++){
echo $mat[$i][$j] . " " ;
}
echo "</br>\n";
}
echo "</br>\n\n";
}
?>
</body>
</html>
- Codice getplan (necessario al dispositivo esterno per sapere quando si deve accendere e quando spegnere):
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
header("Content-Type: application/json");
include('./festività.php');
$today = date("Y-m-d");
$today_name = date("l");
$plans = getpcT1T2($today, $today_name); // funzione descritta in festività php per leggere dal file di testo 'config.txt' gli orari di accensione e spegnimento in base alla data passata in input
$dats = array("pranzo_i" => $plans[0], "pranzo_f" => $plans[1], "cena_i" => $plans[2], "cena_f" => $plans[3]);
echo json_encode($dats); // quando si chiama questa pagina, quest'ultima restituisce un oggetto json con gli orari richiesti
exit();
?>
- Per la visualizzazione della tabella, si devono andare a leggere i valori dal DB delle stanze e in base al valore contenuto modificare il css della riga affinchè si colori di giallo se è uguale a 0, altrimenti colorarla di bianco se uguale a 1.
Fare riferimento al file visualizza.php
- Aprire Arduino IDE con l'ESP8266 scollegato
- File -> Impostazioni -> Cliccare "URL aggiunttivi per il Gestore Schede"
- Aggiungere questo link ->
https://arduino.esp8266.com/stable/package_esp8266com_index.json
- Chiudere e riaprire Arduino IDE
- Strumenti -> Gestore librerie
- Installare: WiFi, , Adafruit ESP8266, ArduinoOTA e AVision_ESP8266
- Caricare il progetto Arduino: ./esp_code/PORTATOVAGLIOLI
- Collegare l'ESP8266
- Selezionare in Strumenti -> Scheda -> ESP8266 Boards -> LOLIN(WEMOS) D1 mini (clone)
- Selezionare la porta COM corrispondente all'ESP8266 appena collegato
Di seguito il codice di PORTATOVAGLIOLI.ino e relativa modifiche da applicare:
#include <time.h>
#include <Wire.h>
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <ESP8266WebServer.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecure.h>
#include <ArduinoOTA.h>
#define exp1_addr 0x3E
#define exp2_addr 0x3F // Define delle costanti/indirizzi globali utilizzati da I2C per gli SX1509
#define exp3_addr 0x70
#define REG_DIR_B 0x0E
#define REG_DIR_A 0x0F // Define delle costanti globali utilizzate
#define REG_DATA_B 0x10
#define REG_DATA_A 0x11
#define REG_PULLUP_A 0x07
#define REG_PULLUP_B 0x06
#define pin_relay 13
#define rows 4 // Define delle costanti globali utilizzate
#define cols 10
WiFiClientSecure client;
HTTPClient http;
ESP8266WebServer webserver(80);
const char* url = "<endpoint_url_handler>/sensore"; // Inserire l'url che gestirà l'aggiornamento del DB dell'applicativo Web
//const char* host = "https://www.poggiolevante.org"; // nel nostro caso sarà poggiolevante.org/appetito/sensore.php
String txt_msg = "";
uint16_t b1,b2,b3;
uint8_t exp1_map[4] = {1, 2, 3, 4}; // Dichiarazione degli array di posizione delle colonne della struttura
uint8_t exp2_map[4] = {5, 10, 0, 0};
uint8_t exp3_map[4] = {6, 7, 8, 9};
String getData, link;
bool exp_errors[] = {false, false, false};
void setup() {
Serial.begin(115200);
WiFi.begin("<wifi_ssid>", "<wifi-psw>"); // Connessione WiFi, nome della rete, password della rete
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.println("Waiting for connection");
}
Serial.println("Connected!");
Serial.print("IP address:\t");
Serial.println(WiFi.localIP());
pinMode(pin_relay, OUTPUT);
Wire.begin();
if (checkConnection(exp1_addr) != 0) { Serial.println("Errore su expander 1"); exp_errors[0] = true; }
if (checkConnection(exp2_addr) != 0) { Serial.println("Errore su expander 2"); exp_errors[1] = true; } // Funzione dichiarata in util.ino inoVerifica della connessione ad ogni expander
if (checkConnection(exp3_addr) != 0) { Serial.println("Errore su expander 3"); exp_errors[2] = true; }
setPullUp(exp1_addr);
setPullUp(exp2_addr); // Funzione dichiarata in util.ino che setta in modalità PULL_UP gli expander
setPullUp(exp3_addr);
setDirectionAsInput(exp1_addr);
setDirectionAsInput(exp2_addr); // Funzione dischiarata in util.ino che setta in input i pin degli expander
setDirectionAsInput(exp3_addr);
init_webserver(); // Inizializzazione del WebServer
init_OTA(); // Inizializzazione OTA, dopo il primo caricamento (tramite cavo) del codice sarà possibile aggiornarlo tramite OTA (Strumenti -> Porta -> Indirizzo IP privato dell'ESP8266)
Serial.println("Looping...");
}
void loop() {
ArduinoOTA.handle();
webserver.handleClient();
b1 = readWordRegister(exp1_addr, REG_DATA_B);
Serial.println(b1, BIN);
b2 = readWordRegister(exp2_addr, REG_DATA_B); // Lettura dei valori delle fotoresistenze da ogni expander
Serial.println(b2, BIN); // Funzione per la lettura dischiarata in util.ino
b3 = readWordRegister(exp3_addr, REG_DATA_B);
Serial.println(b3, BIN);
Serial.println();
String exp1_map_str = "";
String exp2_map_str = "";
String exp3_map_str = "";
for (int i = 0; i < rows; i++) {
String separator = String((i == 3) ? "" : ","); // Costruzione delle stringhe per visualizzare la matrice equivalente dei valori delle fotoresistenze (equivalente alla struttura dei tovaglioli)
exp1_map_str += String(exp1_map[i]) + separator;
exp2_map_str += String(exp2_map[i]) + separator;
exp3_map_str += String(exp3_map[i]) + separator;
}
getData = "?data1=" + String(b1) + "&data2=" + String(b2) + "&data3=" + String(b3)
+ "&map1=" + exp1_map_str + "&map2=" + exp2_map_str + "&map3=" + exp3_map_str; // Costruzione dell'url + query string da passare
link = url + getData;
bool box[rows][cols];
getMatrix(exp1_map, b1, exp2_map, b2, exp3_map, b3, box); // Funzione dichiarata in util.io per la costruzione della matrice di booleani, tramite l'utilizzo degli array di posizione
printMatrix(box); // Funzione dichiarata in util.io per la visualizzazione della matrice equivalente alla struttura sulla seriale
//printMatrixBoolean(box);
Serial.println("Link: " + link); // Visualizzazione del l'url costruito per l'aggiornamento del DB sulla seriale
if (WiFi.status() == WL_CONNECTED) { //Check WiFi connection status
client.setInsecure();
client.connect(url, 443);
http.begin(client, link);
// Chiamata all'url costruito per l'aggiornamento dei dati nel DB
String payload;
if (http.GET() == HTTP_CODE_OK)
payload = http.getString();
//Serial.println("payload:\n"+payload);
delay(1000);
} else {
Serial.println("Error in WiFi connection");
}
delay(3000);
}
Codice di util.ino:
uint16_t readWordRegister(uint8_t deviceAddress, uint8_t registerAddress) {
uint16_t readValue;
uint16_t msb, lsb;
uint8_t byteToRead = 2;
Wire.beginTransmission(deviceAddress);
Wire.write(registerAddress);
Wire.endTransmission(false);
Wire.requestFrom(deviceAddress, byteToRead);
msb = Wire.read() << 8;
lsb = Wire.read();
readValue = msb | lsb;
return readValue;
}
void setPullUp(uint8_t deviceAddress){
Wire.beginTransmission(deviceAddress);
Wire.write(REG_PULLUP_A);
Wire.write(0xFF);
Wire.endTransmission();
Wire.beginTransmission(deviceAddress);
Wire.write(REG_PULLUP_B);
Wire.write(0xFF);
Wire.endTransmission();
}
void setDirectionAsInput(uint8_t deviceAddress){
Wire.beginTransmission(deviceAddress);
Wire.write(REG_DIR_B);
Wire.write(0xFF);
Wire.endTransmission();
Wire.beginTransmission(deviceAddress);
Wire.write(REG_DIR_A);
Wire.write(0xFF);
Wire.endTransmission();
}
byte checkConnection(uint8_t deviceAddress){
byte error;
Wire.beginTransmission(deviceAddress);
error = Wire.endTransmission();
return error;
}
void printMatrix(bool box[][10]){
txt_msg = "<pre>\n 1 2 3 4 5 6 7 8 9 10 \n";
txt_msg +="|---------------------------------------|\n| ";
for(int r = 0; r < rows; r++){
for(int c = 1; c <= cols; c++){
txt_msg += String(box[r][c] ? "1" : "0") + " | ";
}
txt_msg += "\n|---+---+---+---+---+---+---+---+---+---|\n| ";
}
txt_msg = txt_msg.substring(0, txt_msg.length() - 2);
txt_msg += "</pre>";
Serial.println(txt_msg);
}
void getMatrix(uint8_t exp1_map[], uint16_t b1, uint8_t exp2_map[], uint16_t b2, uint8_t exp3_map[], uint16_t b3, bool box[][10]){
int row, col;
for(int x = 0; x < 16; x++){
row = x % rows;
bool bitValue = (b1 & (1 << x)) >> x;
col = exp1_map[x / 4];
box[row][col] = bitValue ? true : false;
if ( x >= 0 && x <= 7){
col = exp2_map[x / 4];
bitValue = (b2 & (1 << x)) >> x;
box[row][col] = bitValue ? true : false;
}
col = exp3_map[x / 4];
bitValue = (b3 & (1 << x)) >> x;
box[row][col] = bitValue ? true : false;
}
}
void printMatrixBoolean(bool box[][10]){
Serial.println();
for(int i = 0; i < rows; i++){
Serial.println();
for(int j = 1; j <= cols; j++){
Serial.print(box[i][j]);
Serial.print(", ");
}
Serial.println();
}
}
/*bool writeByte(uint8_t deviceAddress,uint8_t registerAddress, uint8_t writeValue)
{
Wire.beginTransmission(deviceAddress);
bool result = Wire.write(registerAddress) && Wire.write(writeValue);
uint8_t endResult = Wire.endTransmission();
return result;
}*/
Osservazione: Per una maggiore comprensione del protocollo I2C e di come funzione fare riferimento a:
- Spiegazione
- Documentazione dell'SX1509
- Di seguito viene riportato un'esempio di comunicazione tramite I2C
Codice webserver.ino:
void init_webserver() {
webserver.on("/lighton", [](){ // Per l'accensione dei LED
digitalWrite(pin_relay, HIGH);
webserver.send(200, "text/html", "OK Champ" );
});
webserver.on("/lightoff", [](){ // Per lo spegnimento dei LED
digitalWrite(pin_relay, LOW);
webserver.send(200, "text/html", "OK Champ" );
});
webserver.on("/reset", [](){ // Per il reset dell'ESP8266
webserver.send(200, "text/html", "Ok Champ");
delay(10000);
ESP.restart();
});
webserver.on("/debug", [](){ // Per la visualizzazione della matrice di equivalenza della struttura e il test di funzionamento sugli expande (comporta l'accensione dei LED)
digitalWrite(pin_relay, HIGH);
if (checkConnection(exp1_addr) != 0) { Serial.println("Errore su expander 1"); exp_errors[0] = true; }
if (checkConnection(exp2_addr) != 0) { Serial.println("Errore su expander 2"); exp_errors[1] = true; }
if (checkConnection(exp3_addr) != 0) { Serial.println("Errore su expander 3"); exp_errors[2] = true; }
webserver.send(200, "text/html", txt_msg
+ "<br><br>Expander1: " + (exp_errors[0] ? "Errore" : "OK")
+ "<br><br>Expander2: " + (exp_errors[1] ? "Errore" : "OK")
+ "<br><br>Expander3: " + (exp_errors[2] ? "Errore" : "OK"));
});
webserver.begin();
Serial.println(F("HTTP server started"));
}
void init_OTA(){ // Per il caricamento del codice dell'ESP8266 tramite OTA (Over The Air)
ArduinoOTA.onStart([]() {
Serial.println(F("Inizio aggiornamento OTA"));
SPIFFS.end();
});
ArduinoOTA.onEnd([]() {
ESP.restart();
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
Serial.printf("Avanzamento: %u%%\r", (progress / (total / 100)));
});
ArduinoOTA.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
});
ArduinoOTA.setHostname("Portatovagli");
ArduinoOTA.begin();
Serial.println(F("OTA Avviato!!!"));
}
- Aprire terminale linux
- Installare modulo 'requests' per python (se non lo si è già fatto)
Procededura descritta nella sezione "Requisiti SW"
- Clonare repository oppure scaricare solo i due script presenti in py_code_LED e spostarli/posizionarli in /home/user/
- Spostarsi in /home/user/ e digitare:
pi@antisaladapranzo:~ $ nano plantov.py
- Di seguito il codice di plantov.py e cosa modificare:
from datetime import datetime, timedelta, time from time import sleep import requests data = requests.get("https://<webserver_target>/getplan") # <webserver_targer> -> url del webserver print("Trying to get the page: https://<webserver_target>/getplan", end="") while data.status_code != 200: data = requests.get("https:<webserver_target>/getplan") print("Trying to get the page: https://<webserver_target>/getplan", end="") sleep(5) print("Done!") data_json = data.json() data_json["pranzo_i"] = datetime.strptime(data_json["pranzo_i"], "%H:%M") data_json["cena_i"] = datetime.strptime(data_json["cena_i"], "%H:%M") data_json["pranzo_f"] = datetime.strptime(data_json["pranzo_f"], "%H:%M") data_json["cena_f"] = datetime.strptime(data_json["cena_f"], "%H:%M") now = datetime.now() count = 0 max_time = 50 if now.hour == data_json["pranzo_i"].hour and data_json["pranzo_i"].minute == now.minute: print("Trying to get the page: http://<ip_target>/lighton for lunch .. ", end="") # <ip_target> -> ip dell'ESP8266 count += 1 while (requests.get("http://<ip_target>/lighton")).status_code != 200 and count <= max_time: count += 1 print("Trying to get the page: http://<ip_target>/lighton .. ", end="") sleep(2) elif now.hour == data_json["pranzo_f"].hour and now.minute == data_json["pranzo_f"].minute: print("Trying to get the page: http://<ip_target>/lightoff for lunch .. ", end="") count += 1 while (requests.get("http://<ip_target>/lightoff")).status_code != 200 and count <= max_time: count += 1 print("Trying to get the page: http://<ip_target>/lightoff .. ", end="") sleep(2) elif now.hour == data_json["cena_i"].hour and now.minute == data_json["cena_i"].minute: print("Trying to get the page: http://<ip_target>/lighton for dinner .. ", end="") count += 1 while (requests.get("http://<ip_target>/lighton")).status_code != 200 and count <= max_time: print("Trying to get the page: http://<ip_target>/lighton .. ", end="") count += 1 sleep(2) elif now.hour == data_json["cena_f"].hour and now.minute == data_json["cena_f"].minute: print("Trying to get the page: http://<ip_target>/lightoff for dinner .. ", end="") count += 1 while (requests.get("http://<ip_target>/lightoff")).status_code != 200 and count <= max_time: print("Trying to get the page: http://<ip_target>/lightoff .. ", end="") count += 1 sleep(2) if count == 0: print("Nothing to do") elif count <= 50: print("Done!") else: print("Error") print("BYE")
!IMPORTANTE Assegnare un indirizzo IP Statico all'ESP8266, altrimenti quando verrà spento il DHCP assegnerà un indirizzo IP diverso.
- Salvare con CTRL+O poi premere INVIO
- Chiudere nano con CTRL+X
- Eseguire il terminale con i permessi di root:
pi@antisaladapranzo:~ $ sudo su
- Inserire la password di root
- Digitare:
root@antisaladapranzo:/home/pi# crontab -e
- Aggiungere in coda questo codice:
#pranzo-cena chiamata reset <minuti> <ora> * * 1-7 python3 /home/pi/tovtoreset.py <minuti> <ora> * * 1-7 python3 /home/pi/tovtoreset.py #pranzo-cena chiamata lighton/lightoff * * * * * python3 /home/pi/plantov.py
Osservazione: le righe dopo #pranzo-cena chiamata reset e prima di #pranzo-cena chiamata lighton/lightoff servono per resettare l'ESP8266 prima del suo effettivo funzionamento, per questo si consiglia di inserire un orario parecchio antecedente agli orari di pranzo e cena.
Il progetto è stato pubblicato a Agosto 2023, nella sua prima versione ed è attualmente funzionante.
Software: Tommaso Orlando e Salvatore Incarnato.
Hardware & Costruzione Pannelli Posteriori: Giovanni Pompigna, Pier Francesco Amendola, Sergio Muccilli, Tommaso Orlando e Salvatore Incarnato.
Agosto 2023