Tovaglioli intelligenti

Elencare chi manca a tavola

Un sistema per la verifica delle prenotazioni alla WebApp della mensa di Poggiolevante.

TovaglioloStructure

Open Source e Documentazione:

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.

Specifiche del progetto:

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:

InterfacciaWebPrenotazioni

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.

Wemos D1 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.

LeftStructure RightStructure

Come funziona?

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.

Requisiti HW

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:

Femmina Maschi

  • 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)

Topologia HW del Sistema

Di seguito sono riportate le immagini delle topologia del circuito PCB, Pannelli Posteriori e del singolo slot.

Circuito WEMOS D1 + EXPANDER SX1509

CircuitTopology


Ecco quello da noi costruito:

NewCircuitTop NewCircuitBottomScene NewCircuitBottom

NewCircuitSceno


Circuito pannelli posteriori

SystemCircuit


Ecco lo slot che occupa i vari collegamenti:

Variouswires


Circuito singolo slot

SingleCellCircuit


Ecco un esempio dei nostri:

SempleSlotInside

Qui invece una foto da vicino dei rettangoli di legno rimovibili con fotoresistenza:

SampleSlotBase


Precisazione collegamenti Fotoresistenze - Expander

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:

Circuits

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).

LeftStructureSigned

Successivamente abbiamo collegato le quaterne di cavi al PCB in questa maniera:

ColumnsPosition

Requisiti SW

  • 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
  • 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)

Rep-Tree:

La Repository è cosi composta:

Rep-Tree

  • /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.

Passi da sequire per l'implementazione

SetUp WebServer


  • 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

SetUp ESP8266


  • 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

EspBoardInstallation

  • Strumenti -> Gestore librerie

EspLibraryHandler

  • 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

SampleWireLibrary


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!!!"));
}

SetUp Dispositivo esterno (automazione accensione e spegnimento LED)


  • 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.

Stato attuale del progetto:

Il progetto è stato pubblicato a Agosto 2023, nella sua prima versione ed è attualmente funzionante.

Autori:

Software: Tommaso Orlando e Salvatore Incarnato.

Hardware & Costruzione Pannelli Posteriori: Giovanni Pompigna, Pier Francesco Amendola, Sergio Muccilli, Tommaso Orlando e Salvatore Incarnato.

Agosto 2023