Costruire un datalogger remoto con Raspberry e Arduino
Il progetto che ho realizzato riguarda il monitoraggio di temperatura e umidità all’interno di un locale tecnico. I dati rilevati da un sensore DHT22 vengono acquisti da una Arduino MKR Zero e tramite una shield MKR ETH inviati a un computer Raspberry PI 4 che dopo averli acquisiti li memorizza all’interno di una tabella SQLite.
Il protocollo utilizzato per l’invio dei dati si basa su UDP come abbiamo visto in questo articolo.

I dati sarebbero potuti essere memorizzati in un semplice file di testo ma ho preferito usare un database in modo tale da estrarli in modo rapido utilizzando semplici query. Il database contiene per ora una tabella contenente i seguenti campi:
| Nome Campo | Tipo Campo |
|---|---|
| ID |
INTEGER PRIMARY KEY AUTOINCREMENT
|
| Data |
TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
Temperatura
|
VARCHAR(50) NOT NULL
|
|
Umidita
|
VARCHAR(50) NOT NULL
|
Il codice seguente gira sul Raspberry Pi 4; ho inserito diversi commenti che spiegano le varie istruzioni contenute nel programma:
import sqlite3
import socket
import threading
import time
#IP computer locale
UDP_IP_ADDR = "192.168.178.50"
#Porta computer locale
UDP_PORT_NUMBER = 20002
ip_port_Data = (UDP_IP_ADDR, UDP_PORT_NUMBER)
def client_Receive_Data():
print("Avvio THREAD Receive Data...")
serversocket1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
serversocket1.bind(("192.168.178.30", 20001))
msgToSend = str.encode("DB_OK")
#crea connessione al database
connection = sqlite3.connect('lopro.db')
cursor = connection.cursor()
try:
while True:
data, address = serversocket1.recvfrom(1024)
strData = bytes.decode(data).split(":")
sql = """ INSERT INTO tbl_log(Temperatura, Umidita) VALUES ('{}', '{}');""".format(strData[0], strData[1])
print(sql)
cursor.execute(sql)
connection.commit()
#invia al device una risposta
serversocket1.sendto(msgToSend, ip_port_Data)
time.sleep(1)
except:
print("errore")
msgToSend = str.encode("ERRORE")
serversocket1.sendto(msgToSend, ip_port_Data)
connection.close()
def crea_tabella():
#una volta creata la tabella possiamo evitare di chiamare
#questa funzione ad ogni esecuzione del programma
#crea connessione al database
connection = sqlite3.connect('lopro.db')
cursor = connection.cursor()
#Se esiste la tabella cancellala
#cursor.execute("DROP TABLE IF EXISTS tbl_log")
# Crea la tabella se non esiste
table = """ CREATE TABLE IF NOT EXISTS tbl_log (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
Data TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
Temperatura VARCHAR(50) NOT NULL,
Umidita VARCHAR(50) NOT NULL
); """
cursor.execute(table)
cursor.close()
connection.close()
def list_rows():
print("list rows..")
#crea connessione al database
connection = sqlite3.connect('lopro.db')
sql = """ SELECT * FROM tbl_log;"""
cursor = connection.cursor()
cursor.execute(sql)
rows = cursor.fetchall()
for row in rows:
print(row)
# Close the connection
connection.close()
if __name__=="__main__":
#Rimuovere il commento per creare la tabella
#crea_tabella()
#list_rows()
#Avvio un thred
print("Start program")
t1 = threading.Thread(target=client_Receive_Data)
t1.start()
Questo è invece il codice che gira sulla Arduino MKR Zero; ho inserito diversi commenti che spiegano le varie istruzioni contenute nel programma:
#include <SPI.h>
#include <Ethernet.h>
#include <EthernetUdp.h>
#include <DHT.h>
#define DHTPIN 6
#define DHTTYPE DHT22
// creo un MAC address e un IP address per la scheda Arduino
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192, 168, 178, 50);
//creo un ip address per il raspberry
IPAddress ip_remote(192, 168, 178, 30);
//creo una porta per la scheda Arduino
unsigned int localPort = 20002;
//creo degli array di char per la ricezione e l'invio dei
//dati via udp
char packetBuffer[UDP_TX_PACKET_MAX_SIZE];
char chrBuffer[11];
//altre variabili
String strTempUmid = "";
byte ciclo = 0;
// Creo degli oggetti per l'utilizzo di udp e del
//sensore dht
EthernetUDP Udp;
DHT dht(DHTPIN, DHTTYPE);
void setup() {
//inizializzo lo shield mkr eth
Ethernet.init(5);
//inizializzo la scheda di rete
Ethernet.begin(mac, ip);
//inizializzo la seriale
Serial.begin(9600);
// verifico la ethernet shield
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
Serial.println("MKR ETH non presente o configurata in modo errato!");
while (true) {
delay(1);
}
}
//inizializzo l'oggetto udp
Udp.begin(localPort);
//inizializzo l'oggetto dht
dht.begin();
}
void loop() {
//verifico presenza pacchetti udp in arrivo
int packetSize = Udp.parsePacket();
//se coi sono pacchetti
if (packetSize) {
//Serial.println(packetSize);
//leggo i pacchetti e gli inserisco nell'array di char
Udp.read(packetBuffer, UDP_TX_PACKET_MAX_SIZE);
//printo i dati ricevuti
Serial.println(packetBuffer);
}
//leggo umidità e temperatura dal sensore dht
float h = dht.readHumidity();
float t = dht.readTemperature();
//creo una stringa del tipo temp:umid
strTempUmid = "";
strTempUmid.concat(t);
strTempUmid.concat(":");
strTempUmid.concat(h);
//printo la stringa sul ser mon
Serial.println(strTempUmid);
//converto la stringa in un array di char
strTempUmid.toCharArray(chrBuffer, strTempUmid.length());
//inizializzo la connessione con ip e porta del Raspberry
Udp.beginPacket(ip_remote, 20001);
//Scrivo i dati nel buffer
Udp.write(chrBuffer);
//invio i dati
Udp.endPacket();
//attendo un minuto prima del prossimo invio
for (ciclo = 0; ciclo < 60; ciclo++)
delay(1000);
}
Ogni minuto viene inviata una unica stringa contenente i dati di temperatura e umidita che il codice python provvederà a separate in due dati distinti per essere caricati sul database.
Memorizzare solo le variazioni significative su Raspberry SQLite
Possiamo ottimizzare lo spazio sulla tabella (e di conseguenza sul filesystem del Raspberry) memorizzando solo i dati che hanno una variazione significativa. In questo modo il monitoraggio rimane costante perché comunque l’Arduino legge e invia i dati ogni minuto ma posso risparmiare spazio sul database salvando solo le variazioni significative. Sulla tabella memorizzo anche il timestamp e quindi posso ricostruire l’andamento di temperatura e umidità interpolando i dati.
Il codice seguente mostra la modifica alla funzione client_Receive_Data() in modo tale da memorizzare solo i dati che hanno una variazione di 0.5 rispetto ai dati della lettura precedente rilevata dalla scheda Arduino:
def client_Receive_Data():
print("Avvio THREAD Receive Data...")
serversocket1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
serversocket1.bind(("192.168.178.30", 20001))
# crea connessione al database
connection = sqlite3.connect('lopro.db')
cursor = connection.cursor()
#creo due variabili per memorizzare i dati delle
#letture di temp e umid
temp_prec = 0
umid_prec = 0
try:
while True:
data, address = serversocket1.recvfrom(1024)
strData = bytes.decode(data).split(":")
#verifico se i dati appena letti differiscono di 0.5 dai valori della lettuare precedente
if (abs(temp_prec - float(strData[0])) > 0.5) or (abs(umid_prec - float(strData[1])) > 0.5) :
print("Variazione di Temp e Hum")
sql = """ INSERT INTO tbl_log(Temperatura, Umidita) VALUES ('{}', '{}');""".format(strData[0], strData[1])
#memorizzo i dati per poterli confrontare con quelli
#che arrivano dalla scheda arduino
temp_prec = float(strData[0])
umid_prec = float(strData[1])
#visualizzo la stringa sql
print(sql)
#eseguo la stringa sql insert avviando una transazione
cursor.execute(sql)
#confermo la transazione
connection.commit()
# invia al device una risposta
msgToSend = str.encode("DB_OK")
serversocket1.sendto(msgToSend, ip_port_Data)
else:
print("Nessuna variazione di Temp e Hum")
# invia al device una risposta
msgToSend = str.encode("DB_NOK")
serversocket1.sendto(msgToSend, ip_port_Data)
time.sleep(1)
except:
print("errore")
msgToSend = str.encode("ERRORE")
serversocket1.sendto(msgToSend, ip_port_Data)
connection.close()
L’utilizzo congiunto di Raspberry e Arduino ci permette di realizzare progetti che superano i limiti dati dalle singole schede.
Related posts
Articoli popolari
Sorry. No data so far.
.Net micro framework Arduino Arduino Webserver Domotica Flyport I2C IOT Netduino OpenPicus raspberry RTC Speed Test