Come fare un datalogger bluetooth per temperatura e umidità con Arduino MEGA

A cosa serve un datalogger?

Un datalogger è un dispositivo elettronico che acquisisce dei parametri fisici (per esempio temperatura, umidità, pressione atmosferica, inquinanti etc) in precisi intervalli temporali e li salva su un dispositivo permanente di memoria (come per esempio una SD card) oppure li trasmette ad una piattaforma cloud per essere elaborati e visualizzati.

Descrizione del progetto

In questo progetto realizzaremo un semplice datalogger che acquisisce i valori di temperatura ed umidità ambientali, li salva in un file all’interno di una SD card e li mostra in tempo reale su un display a cristalli liquidi.

Inoltre questo datalogger, gestito da una scheda Arduino MEGA, dispone di un modulo bluetooth per poterlo interrogare (tramite appositi script in Python) e visualizzare i dati memorizzati (e non solo) senza dover togliere la SD card dal modulo.

Completa il tutto un modulo RTC (Real Time Clock) che ci serve per tenere traccia della data e ora di acquisizione di ciascun dato.

Di che componenti abbiamo bisogno?

La lista dei componenti non è particolarmente lunga:

Questo è un completo datalogger, infatti memorizza su un file CSV nella SD card i valori di temperatura e umidità rilevati oltre all’orario di misurazione ogni 10 minuti (questo tempo può essere impostato nello sketch ma suggeriamo di non scendere sotto i 10 minuti altrimenti il file diventa troppo grande). Lo sketch crea automaticamente il path per il file di log all’interno della SD card usando il formato /templog/yy/mm/dd.

Il file di log può essere letto inserendo la SD card nel lettore apposito del proprio PC oppure usando la connessione bluetooth.

Realizzazione

Puoi scaricare lo sketck e lo script Python da qui:

Per la realizzazione del circuito puoi fare riferimento allo schematico Fritzing qui sotto:

Schematico del datalogger
Schematico del datalogger

Il trimmer serve per regolare la luminosità del display.

Dopo aver fatto i collegamenti, carica lo sketch su Arduino.

Inserisci la SD card nel lettore del vostro PC e crea al suo interno una cartella che chiamerai templog. In questa cartella il datalogger salverà i files dei dati acquisiti (suddivisi in sottocartelle per anno, mese e giorno).

Inserisci la SD card nel suo modulo e accendi il tuo Arduino. La scheda inizierà subito a misurare la temperatura e l’umidità dell’ambiente e a salvarle nella SD card.

Il modulo bluetooth HC-05
Il modulo bluetooth HC-05

Impostazioni e comandi interni dello sketch

Se non hai voglia di vedere come funziona lo sketch ma hai fretta di impostarlo e farlo funzionare, eccoti una lista dei parametri che puoi impostare e dei comandi interni.

Nello sketch puoi impostare alcuni parametri:

  • max_battery_voltage se Arduino è alimentato a batteria puoi impostare qui la sua tensione (il display mostrerà il livello di carica con una icona)
  • minutes_updatefile lo sketch aggiornerà il log file ogni “minutes_updatefile” minuti
  • seconds_updatedatehour lo sketch aggiornerà la data e l’ora sul display LCD ogni “seconds_updatedatehour” secondi
  • seconds_updatemeasurelcd lo sketch aggiornerà la temperatura e l’umidità sul display LCD ogni “seconds_updatemeasurelcd” secondi
  • seconds_updatemeasurebattery lo sketch aggiornerà la misura della carica della batteria ogni “seconds_updatemeasurebattery” secondi
  • pathbase questa è la cartella iniziale dove sono contenuti i file di log nella SD card (il valore di default è templog/)

Lo sketch dispone di alcuni comandi interni:

  • rf&: il comando “read file” (legge il file)
  • sf&: il comando “size file” (restituisce la dimensione del file)
  • mth&: il comando “measure t/h” (restituisce i valori correnti di temperatura e umidità)
  • rdt&: il comando “read date/time” (restituisce la data e l’ora)
  • tsd&: il comando “type sd card” (restituisce il tipo di SD card)
  • ssd&: il comando “size sd card” (restituisce la dimensione della SD card)
  • fm&: il comando “free memory size” (restituisce la dimensione della parte libera di memoria del microcontrollore)
  • sdt&: il comando “update date and time” (aggiorna la data e l’ora)
  • rbt&: il comando “read voltage battery” (restituisce la tensione della batteria)

Interagiamo col datalogger tramite lo script Pyhton

Puoi interagire col data logger tramite la connessione bluetooth e lo script test.py. Dovrai fare prima il pairing del bluetooth del tuo PC con quello del datalogger.

Una volta scaricato lo script test.py dal link sopra, devi impostare al suo interno il nome e il MAC address del tuo modulo bluetooth HC-05 (per esempio, nel nostro script sono BT1 e 00:13:04:10:06:20).

Potrai quindi lanciare lo script dalla shell seguendo la sintassi dei comandi qui sotto (lo script deve essere eseguibile).

Ecco alcuni esempi di utilizzo:

  • python test.py -dn BT1 -cl “mth&?” legge temperatura e umidità
  • python test.py -dn BT1 -cl “rdt&?” legge data e ora
  • python test.py -dn BT1 -cl “sf&14/01/20?” legge la dimensione del file specificato (nel formato: yy/mm/dd)
  • python test.py -dn BT1 -cl “rf&14/01/20?” legge il contenuto del file specificato (nel formato: yy/mm/dd)
  • python test.py -dn BT1 -cl “tsd&?” legge il tipo di SD card
  • python test.py -dn BT1 -cl “ssd&?” legge la dimensione della SD card
  • python test.py -dn BT1 -cl “fm&?” legge la dimensione della parte libera di memoria del microcontrollore
  • python test.py -dn BT1 -cl “sdt&2014&01&21&14&30&00?” imposta data e ora (nel formato: yyyy, mm, dd, hh, mm, ss)
  • python test.py -dn BT1 -cl “rbt&?” legge la tensione della batteria

Vediamo ora alcune immagini che riprendono il datalogger in funzione:

Il datalogger in funzione
Il datalogger in funzione

Zoom del display
Zoom del display

Analizziamo il codice

Per i più ardimentosi che volessero vedere come è fatto il codice, presenteremo nei prossimi due paragrafi lo sketch Arduino e lo script Python.

Lo sketch Arduino

Lo script inizia con l’inclusione della libreria per il DHT22, l’istanziazione dell’oggetto dht e la definizione del pin di lettura dei dati dal sensore:

#include "DHT.h" // Load DHT library
#define DHTPIN 10 // Setting pin 10 for data coming from DHT
#define DHTTYPE DHT22   // DHT 22  
//#define DHTTYPE DHT11   // DHT 11    !!  DHT11 DOES NOT provide decimal digits for temperature and humidity, DHT22 does.
DHT dht(DHTPIN, DHTTYPE);

Segue l’inclusione delle librerie per il modulo RTC, per il bus I2C e per la gestione della memoria:

#include <Wire.h>
#include "RTClib.h"

RTC_DS1307 rtc;

#include "MemoryFree.h"

Infine ci sono l’inclusione della libreria del display LCD e la sua inizializzazione e l’inclusione della libreria per il modulo SD card e la definizione delle sue variabili:

// include the library code:
#include <LiquidCrystal.h>

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(7, 6, 5, 4, 3, 2);


// include the SD library:
#include <SD.h>
Sd2Card card;
SdVolume volume;
SdFile root;

Successivamente definiamo le icone che rappresentano lo stato di carica della batteria e che verranno rappresentate sul display LCD. A titolo di esempio vediamo l’icona relativa alla batteria scarica:

byte battery_icon_0[8] = {
  B01110,
  B10001,
  B10001,
  B10001,
  B10001,
  B10001,
  B11111,
};

e l’icona relativa alla batteria carica:

byte battery_icon_5[8] = {
  B01110,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
};

Gli “1” accenderanno il pixel corrispondente mentre gli “0” lo terranno spento, componendo così l’icona della batteria.

Segue poi la sezione di definizione dei parametri che abbiamo visto precedentemente:

int battery_voltage_sensor;
float battery_voltage;
float max_battery_voltage = 9.0;
char state, state1;
int minutes_updatefile = 10;
int seconds_updatedatehour = 30;
int seconds_updatemeasurelcd = 5;
int seconds_updatemeasurebattery = 10;
String pathbase = "templog/";
byte rx;    // received char (from serial port)

Nella funzione setup inizializziamo il bus I2C, le porte seriali e il modulo RTC:

Wire.begin();
  // set up the LCD's number of columns and rows: 
  lcd.begin(16, 2);
  
  Serial.begin(57600);    // Serial monitor communication  default 57600
  Serial3.begin(115200);    // BT communication  default 9600
  
  rtc.begin();
  
  if (! rtc.isrunning()) {
    //Serial.println("RTC is NOT running!");
    // following line sets the RTC to the date & time this sketch was compiled
    rtc.adjust(DateTime(__DATE__, __TIME__));
  }

La funzione loop contiene varie parti:

  const unsigned long totMinutes = minutes_updatefile * 60 * 1000UL;
  static unsigned long lastSampleTime = 0 - totMinutes;  // initialize such that a reading is due the first time through loop()
  unsigned long now = millis();
  if (now - lastSampleTime >= totMinutes)
  {
     lastSampleTime += totMinutes;
     updatefile(); 
  }


  const unsigned long totMinutes1 = seconds_updatedatehour * 1000UL;
  static unsigned long lastSampleTime1 = 0 - totMinutes1;  // initialize such that a reading is due the first time through loop()
  unsigned long now1 = millis();
  if (now1 - lastSampleTime1 >= totMinutes1)
  {
     lastSampleTime1 += totMinutes1;
     lcd.setCursor(0, 0);
     lcd.print(date_hour_1());
  }


  const unsigned long totMinutes2 = seconds_updatemeasurelcd * 1000UL;
  static unsigned long lastSampleTime2 = 0 - totMinutes2;  // initialize such that a reading is due the first time through loop()
  unsigned long now2 = millis();
  if (now2 - lastSampleTime2 >= totMinutes2)
  {
     lastSampleTime2 += totMinutes2;
     lcd.setCursor(0, 1);
     lcd.print(measure_lcd());
  }

Questa prima parte si occupa di aggiornare il file di log nella SD card chiamando ciclicamente la funzione updatefile() secondo le tempistiche impostate e si occupa pure di aggiornare la stampa di data, ora, temperatura e umidità sul display LCD.

Il secondo blocco:

  const unsigned long totMinutes3 = seconds_updatemeasurebattery * 1000UL;
  static unsigned long lastSampleTime3 = 0 - totMinutes3;  // initialize such that a reading is due the first time through loop()
  unsigned long now3 = millis();
  if (now3 - lastSampleTime3 >= totMinutes3)
  {
     lastSampleTime3 += totMinutes3;
    
     battery_voltage_sensor = analogRead(A0); 
     battery_voltage = battery_voltage_sensor * (max_battery_voltage / 1023.0);
     //Serial.println(battery_voltage);
     
     if (battery_voltage > 8.5 and battery_voltage <= 9.0)
      {
        lcd.createChar(0, battery_icon_5);
      }
      else if (battery_voltage > 8.0 and battery_voltage <= 8.5)
      {
        lcd.createChar(0, battery_icon_4);
      }
      else if (battery_voltage > 7.5 and battery_voltage <= 8.0)
      {
        lcd.createChar(0, battery_icon_3);
      }
      else if (battery_voltage > 7.0 and battery_voltage <= 7.5)
      {
        lcd.createChar(0, battery_icon_2);
      }
      else if (battery_voltage > 6.5 and battery_voltage <= 7.0)
      {
        lcd.createChar(0, battery_icon_1);
      }
      else if (battery_voltage > 6.0 and battery_voltage <= 6.5)
      {
        lcd.createChar(0, battery_icon_0);
      }
      else
      {
        lcd.createChar(0, battery_icon_0);
      }

     lcd.setCursor(15, 0);  
     lcd.write(byte(0));
  }

si occupa di leggere la tensione della batteria tramite la riga

battery_voltage_sensor = analogRead(A0);

e decide quale delle icone indicanti lo stato di carica della batteria deve essere stampata sul display LCD.

Segue poi la parte:

String cmd = "";
    while(1) {
      if(Serial3.available() > 0) {
        rx = Serial3.read();
        cmd = cmd + (char)rx;
        //if(rx != 13){cmd = cmd + (char)rx;}    // 13 is ASCII code for carriage return which usually comes after '?'
        if(rx == 63){                            // 63 is ASCII code for '?', our "end command"
          break;
        }  
      }                 
    }
    Serial3.flush(); // flush the serial buffer 
    cmd.replace("\r\n", "");

che legge ciò che arriva dalla porta seriale 3 (cioè i comandi che arrivano dall’esterno tramite il modulo bluetooth) e infine la parte:

Serial.println(cmd);
if (cmd.substring(0,3) == "rf&") // I received the "read file" command
{
  unsigned long startb = getValue(cmd, '&', 2).toInt();
  unsigned long stopb = getValue(cmd, '&', 3).toInt();
  String path = getValue(cmd, '&', 1);                          
  path.replace("?", "");
  readfile(path, startb, stopb);   
} //else {Serial.println("Not a valid command-");}

if (cmd.substring(0,3) == "sf&") // I received the "size file" command
{
  String path = getValue(cmd, '&', 1);                          
  path.replace("?", "");
  sizefile(path);  
} 

if (cmd.substring(0,4) == "mth&") // I received the "measure t/h" command
{
  Serial3.print( "*" + measure_sd() + "#");  
}  

if (cmd.substring(0,4) == "rdt&") // I received the "read date/time" command
{
  Serial3.print("*" + date_hour() + "#");  
} 

if (cmd.substring(0,4) == "tsd&") // I received the "type sd card" command
{
  Serial3.print("*" + typesdcard() + "#");  
} 

if (cmd.substring(0,4) == "ssd&") // I received the "size sd card" command
{
  Serial3.print("*" + sizesdcard() + " MB#");  
} 

if (cmd.substring(0,3) == "fm&") // I received the "free memory size" command
{
  Serial3.print("*" + (String)freeMemory() + "#");  
} 

if (cmd.substring(0,4) == "sdt&") // I received the "update date and time" command
{
  updatedatetime(cmd);  
} 
if (cmd.substring(0,4) == "rbt&") // I received the "read voltage battery" command
{
  Serial3.print("*"); 
  Serial3.print(battery_voltage);
  Serial3.print("V#");
} 

che interpreta i dati arrivati dalla seriale 3, capisce quale comando è stato mandato e agisce di conseguenza.

Seguono, dopo il loop, le funzioni di appoggio:

  • updatedatetime: aggiorna/imposta la data e l’ora del modulo RTC
  • sizesdcard: restituisce la grandezza della SD card
  • typesdcard: restituisce il tipo di SD card
  • readfile: legge il file di log dalla SD card
  • getValue: splitta la stringa sul carattere separatore dato e restituisce la sottostringa indicata
  • sizefile: restituisce la grandezza del file di log
  • measure_lcd: restituisce una stringa con le letture di temperatura ed umidità adatta ad essere scritta sul display
  • measure_sd: restituisce una stringa con le letture di temperatura ed umidità adatta ad essere scritta sulla SD card
  • date_hour e date_hour_1: restituiscono una stringa con la data e l’ora
  • updatefile: aggiorna il file di log

Lo script Python

Lo script inizia con l’importazione delle librerie necessarie:

import bluetooth
import argparse
import sys

Come già specificato sopra, viene inserito il nome e l’indirizzo del modulo bluetooth del datalogger (nell’esempio rispettivamente BT1 e 00:13:04:10:06:20) :

target_diz = {"BT1" : "00:13:04:10:06:20"}    # put here the names and addresses of your Bluetooth devices

Successivamente vengono analizzati i parametri in ingresso allo script:

parser = argparse.ArgumentParser()

parser.add_argument('-dn', '--device_name', help = "the name of the Bluetooth device")
parser.add_argument('-cl', '--command_line', help = "the command line") 
  
try:
  args = parser.parse_args()
except argparse.ArgumentError, e:
  print "Error %d: %s" % (e.args[0],e.args[1])
  sys.exit(1)

if args.device_name == None:
  print "\nYou must put the name of the Bluetooth device\n"
  sys.exit(1)

if args.command_line == None:
  print "\nYou must put the command line\n"
  sys.exit(1)
  
  
device_name =  args.device_name
command_line = args.command_line

Viene poi definita una funzione che legge ciò che arriva dal bluetooth:

def read_data(sock):
  data = ""
  while(1):
    char = sock.recv(1)
    data = data + char
    if char == "#": break
  return data

La parte successiva si occupa di stabilire la connessione col modulo bluetooth del datalogger:

bd_addr = target_diz[device_name]
port = 1 
 
sock=bluetooth.BluetoothSocket( bluetooth.RFCOMM )

sock.connect((bd_addr, port))

select_command = command_line.split("&")[0]

La parte finale si occupa di mandare i comandi al bluetooth, riceverne la risposta e stamparla sulla shell:

if select_command == "sf":
  sock.send(command_line)
  print read_data(sock)
elif select_command == "rf":
  cmd_size = "sf&" + command_line.split("&")[1]
  sock.send(cmd_size)
  size = read_data(sock).replace("*","").replace("#", "").strip()
  cmd = command_line.replace("?", "&0&") + size + "?"
  sock.send(cmd)
  print read_data(sock)
elif select_command == "mth":
  sock.send(command_line)
  print read_data(sock)
elif select_command == "rdt":
  sock.send(command_line)
  print read_data(sock)
elif select_command == "tsd":
  sock.send(command_line)
  print read_data(sock)
elif select_command == "ssd":
  sock.send(command_line)
  print read_data(sock)
elif select_command == "fm":
  sock.send(command_line)
  print read_data(sock)
elif select_command == "sdt":
  sock.send(command_line)
elif select_command == "rbt":
  sock.send(command_line)
  print read_data(sock)
  
  
sock.close()

Newsletter

Se vuoi essere aggiornato sui nuovi articoli, iscriviti alla newsletter. Prima dell’iscrizione alla newsletter leggi la pagina Privacy Policy (UE)

Se ti vuoi disiscrivere dalla newsletter clicca sul link che troverai nella mail della newsletter.

Inserisci il tuo nome
Inserisci la tua email
0 0 votes
Valutazione articolo
guest
0 Commenti
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
Torna in alto