How to make a bluetooth datalogger for temperature and humidity using Arduino MEGA.

What is a data logger for?

A data logger is an electronic device which acquires physical parameters (for example temperature, humidity, atmospheric pressure, pollutants, etc.) in precise time intervals and saves them on a permanent memory device (such as an SD card) or transmits them to a cloud platform to be processed and displayed.

Description of the project

In this project we will create a simple data logger that acquires ambient temperature and humidity values, saves them in a file on an SD card and shows them in real time on a liquid crystal display.

Furthermore, this datalogger, managed by an Arduino MEGA board, has a bluetooth module to be able to interrogate it (through specific Python scripts) and view the stored data (and not only) without having to remove the SD card from the module.

The whole is completed by an RTC (Real Time Clock) module that we need to keep track of the date and time of acquisition of each data.

What components do we need?

The list of components is not particularly long:

This is a complete data logger, in fact it stores the measured temperature and humidity values ​​in a CSV file on the SD card as well as the measurement time every 10 minutes (this time can be set in the sketch but we suggest not going below 10 minutes otherwise the file becomes too large). The sketch automatically creates the path to the log file on the SD card using the format /templog/yy/mm/dd.

The log file can be read by inserting the SD card into the appropriate reader of your PC or using the bluetooth connection.

Realization

You can download the sketch and Python script from here:

For the realization of the circuit you can refer to the Fritzing schematic below:

Schematic of the datalogger
Schematic of the datalogger

The trimmer is used to adjust the brightness of the display.

After making the connections, upload the sketch to the Arduino.

Insert the SD card into the reader of your PC and create a folder inside which you will call templog. The datalogger will save the acquired data files in this folder (divided into subfolders by year, month and day).

Insert the SD card into its module and turn on your Arduino. The card will immediately start measuring the temperature and humidity of the environment and save them on the SD card.

The HC-05 bluetooth module
The HC-05 bluetooth module

Internal sketch settings and commands

If you don’t want to see how the sketch works but you are in a hurry to set it up and make it work, here is a list of the parameters you can set and the internal commands.

In the sketch you can set some parameters:

  • max_battery_voltage if the Arduino is battery powered you can set its voltage here (the display will show the charge level with an icon)
  • minutes_updatefile the sketch will update the log file every “minutes_updatefile” minutes
  • seconds_updatedatehour the sketch will update the date and time on the LCD every “seconds_updatedatehour” seconds
  • seconds_updatemeasurelcd the sketch will update the temperature and humidity on the LCD every “seconds_updatemeasurelcd” seconds
  • seconds_updatemeasurebattery the sketch will update the battery charge measurement every “seconds_updatemeasurebattery” seconds
  • pathbase this is the initial folder where the log files are contained in the SD card (the default is templog/)

The sketch has some internal commands:

  • rf&: command “read file” (reads the file)
  • sf&: command “size file” (returns the size of the file)
  • mth&: command “measure t/h” (returns the current temperature and humidity values)
  • rdt&: command “read date/time” (returns date and time)
  • tsd&: command “type sd card” (returns the type of SD card)
  • ssd&: command “size sd card” (returns the size of the SD card)
  • fm&: command “free memory size” (returns the size of the free memory of the microcontroller)
  • sdt&: command “update date and time” (updates date and time)
  • rbt&: command “read voltage battery” (returns battery voltage)

Let’s interact with the datalogger via the Python script

You can interact with the data logger via the bluetooth connection and the test.py script. You will first need to pair your PC’s bluetooth with that of the datalogger.

Once you have downloaded the test.py script from the link above, you need to set the name and MAC address of your HC-05 bluetooth module inside it (for example, in our script they are BT1 and 00:13:04:10:06: 20).

You can then launch the script from the shell by following the command syntax below (the script must be executable).

Here are some examples of use:

  • python test.py -dn BT1 -cl “mth&?” reads temperature and humidity
  • python test.py -dn BT1 -cl “rdt&?” read date and time
  • python test.py -dn BT1 -cl “sf&14/01/20?” reads the size of the specified file (in the format: yy/mm/dd)
  • python test.py -dn BT1 -cl “rf&14/01/20?” reads the contents of the specified file (in the format: yy/mm/dd)
  • python test.py -dn BT1 -cl “tsd&?” reads the type of SD card
  • python test.py -dn BT1 -cl “ssd&?” reads the size of the SD card
  • python test.py -dn BT1 -cl “fm&?” reads the size of the free memory of the microcontroller
  • python test.py -dn BT1 -cl “sdt&2014&01&21&14&30&00?” sets date and time (in the format: yyyy, mm, dd, hh, mm, ss)
  • python test.py -dn BT1 -cl “rbt&?” reads the battery voltage

Now let’s see some images that show the data logger working:

The data logger working
The data logger working

Display zoom
Display zoom

Let’s analyze the code

For the more daring who want to see how the code is made, we will present the Arduino sketch and the Python script in the next two paragraphs.

The Arduino sketch

The script starts with the inclusion of the DHT22 library, the instantiation of the dht object and the definition of the sensor reading pin:

#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);

Following there is the inclusion of libraries for the RTC module, for the I2C bus and for memory management:

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

RTC_DS1307 rtc;

#include "MemoryFree.h"

Finally there are the inclusion of the LCD display library and its initialization and the inclusion of the library for the SD card module and the definition of its variables:

// 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;

Subsequently we define the icons that represent the state of charge of the battery and that will be represented on the LCD display. As an example, let’s see the icon relating to the low battery:

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

and the charged battery icon:

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

The “1s” will turn on the corresponding pixel while the “0s” will keep it off, thus composing the battery icon.

Then follows the definition section of the parameters that we have seen previously:

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)

In the setup function we initialize the I2C bus, the serial ports and the RTC module:

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__));
  }

The loop function contains several parts:

  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());
  }

This first part takes care of updating the log file on the SD card by cyclically calling the updatefile() function according to the times set and also takes care of updating the printout of date, time, temperature and humidity on the LCD display.

The second block:

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

takes care of reading the battery voltage via the line:

battery_voltage_sensor = analogRead(A0);

and decides which of the icons indicating the battery charge status should be printed on the LCD display.

Then follows the part:

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", "");

which reads what arrives from serial port 3 (i.e. the commands arriving from outside via the bluetooth module) and finally the part:

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

which interprets the data arriving from the serial 3 port, understands which command has been sent and acts accordingly.

The support functions follow after the loop:

  • updatedatetime: updates/sets the date and time of the RTC module
  • sizesdcard: returns the size of the SD card
  • typesdcard: returns the type of SD card
  • readfile: reads log file from SD card
  • getValue: splits the string on the given separator character and returns the given substring
  • sizefile: returns the size of the log file
  • measure_lcd: returns a string with the temperature and humidity readings suitable for being written on the display
  • measure_sd: returns a string with temperature and humidity readings suitable to be written on the SD card
  • date_hour e date_hour_1: they return a string with the date and time
  • updatefile: updates the log file

The Python script

The script starts with importing the necessary libraries:

import bluetooth
import argparse
import sys

As already specified above, the name and address of the bluetooth module of the data logger are entered (in the example respectively BT1 and 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

Subsequently the parameters entering the script are parsed:

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

Then a function is defined that reads what comes from bluetooth:

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

The next part deals with establishing the connection with the bluetooth module of the data logger:

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

sock.connect((bd_addr, port))

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

The final part deals with sending commands to bluetooth, receiving the response and printing it on the 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

If you want to be informed about the release of new articles, subscribe to the newsletter. Before subscribing to the newsletter read the page Privacy Policy (UE)

If you want to unsubscribe from the newsletter, click on the link that you will find in the newsletter email.

Enter your name
Enter your email
0 0 votes
Article Rating
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
Scroll to Top