Arduino + Raspberry= Weather station with webcam (Part Two: the meteo data acquisition)

Hi geek boys and girls!

I hope you passed a very nice Easter holidays. Unfortunately I had the flu during these days…so no great funny time for me! 😦

…But I spent some time developing my weather station (I wrot the code for meteo data acquisition, and then I succesfully connected to the Raspberry a webcam and also a internet dongle for the remote access to the weather station).

 

In this post I will talk about the meteo data acquisition from Arduino and the consequent data trasmission from Arduino to Raspberry via I2C (see my previous post in order to activate the I2C link).

I decided, after some unfruitful (or simply too diffcult to implement) experiment with analog sensors, to use only digital sensors to acquire with Arduino temperature, barometric pressure and relative umidity. So I bought these sensors from Robot Italy (my preferred online store for embedded resources… as you know I love all italian products/services 😉 ):

 

This is the final circuit I’ve realized after some very basic integration test (click on the image for the zoom):

Centralina_meteo_v2_bbNote that I used a resistor of 4.7 KOhm instead 5 KOhm, as suggested on the DHT11 datasheet.

Ok, now it’s time to code some litttle skecth. Let’s start with Arduino.

In order to communicate with DHT11 and with MPL115A1 I used two well known libraries.

I took the DHT11 library from Arduino Playground and I took the MPL115A1 lib from the github of SMacarena (great appreciation for these very good works, and my sincere thanks to the authors 😉 ).

After some cut/paste/washing/ironing/rewashing and some other (not too many,you know me! ;-)) tests, this is the final Arduino code for the meteo data acquisition from sensors:

 

#include <SPI.h>
#include <MPL115A1.h>
#include <dht11.h>
#include <Wire.h> //I2C library

#define __DEBUG__ //note: I used for debug a ht1632c led display
                  //If you can't/don't use it, undef this macro! :-)
#ifdef __DEBUG__
#include <ht1632c.h>

//debug display
ht1632c dotmatrix = ht1632c(&PORTD, 7, 6, 4, 5, GEOM_32x16, 2);
#endif

//i2c settings
#define SLAVE_ADDRESS 0x04
int number_command = 0;
float value_to_send=0.0;

//I2C commands
#define TEMP 1
#define HUMI 2
#define PRES 3
#define DEW 4

//out pint for DHT11 sensor
#define DHT11PIN 2

//sensors
MPL115A1 Pressure_sensor;
dht11 DHT11_sensor;

//current atmo values
float current_pressure=0.0;
float current_humidity=0.0;
float current_temperature=0.0;
double current_dewpoint=0.0;

void setup() {
  //serial setup
  Serial.begin(115200);
  //initialize pressure sensor
  Pressure_sensor.begin();

  // initialize i2c as slave
  Wire.begin(SLAVE_ADDRESS);

  // define callbacks for i2c communication
  Wire.onReceive(receiveData);
  Wire.onRequest(sendData);
#ifdef __DEBUG__
  dotmatrix.clear();
  dotmatrix.setfont(FONT_5x8);
#endif
}

void loop() {
  //take & save pressure values
  current_pressure = GetPressure();
  GetTemp_Humidity_DewPoint();

  //print current valueson serial ...debug (remove it if you want)!
  Serial.print(current_pressure);
  Serial.print(" hPa\n");

  Serial.print (current_humidity);
  Serial.print(" % Humidity\n");

  Serial.print (current_temperature);
  Serial.print(" °C\n");

  Serial.print (current_dewpoint);
  Serial.print(" Dew point (°C)\n");
#ifdef __DEBUG__
  dotmatrix.clear();
  char tmp[20] = "";
  //pressure
  sprintf(tmp, "P=%dhPa", (int)current_pressure);
  byte len = strlen(tmp);
  for (int i = 0; i < len; i++)
    dotmatrix.putchar(5*i, 0, tmp[i], ORANGE);

  //temperaure
  sprintf(tmp, "T=%dC", (int)current_temperature);
  len = strlen(tmp);
  for (int i = 0; i < len; i++)
  dotmatrix.putchar(5*i, 8, tmp[i], RED);
  //humidity
  sprintf(tmp, "H=%d%%", (int)current_humidity);
  len = strlen(tmp);
  for (int i = 0; i < len; i++)
     dotmatrix.putchar(32+5*i, 8, tmp[i], GREEN);
  dotmatrix.sendframe();

#endif
  //delay
  delay(5000);
}

//Acquisition functions
//from sensors
float GetPressure()
{
  float hPa = Pressure_sensor.pressure();
  return hPa;
}

void GetTemp_Humidity_DewPoint()
{
  //take & save DHT11 values (temp + rel. humidity)
  int chk = DHT11_sensor.read(DHT11PIN);
  switch (chk) //check errors
  {
    case DHTLIB_OK:
      Serial.println("read OK");

      //read values
      current_humidity=(float)(DHT11_sensor.humidity);
      current_temperature=(float)(DHT11_sensor.temperature);

      //dew point
      current_dewpoint=dewPoint(DHT11_sensor.temperature, DHT11_sensor.humidity);
    break;

    case DHTLIB_ERROR_CHECKSUM:
      Serial.println("Checksum error");
    break;
    case DHTLIB_ERROR_TIMEOUT:
      Serial.println("Time out error");
    break;

   default:
     Serial.println("Unknown error");
   break;
  }
}

//dewpoint function (taken from http://playground.arduino.cc/main/DHT11Lib)
// dewPoint function NOAA
// reference (1) : http://wahiduddin.net/calc/density_algorithms.htm
// reference (2) : http://www.colorado.edu/geography/weather_station/Geog_site/about.htm
//
double dewPoint(double celsius, double humidity)
{
  // (1) Saturation Vapor Pressure = ESGG(T)
  double RATIO = 373.15 / (273.15 + celsius);
  double RHS = -7.90298 * (RATIO - 1);
  RHS += 5.02808 * log10(RATIO);
  RHS += -1.3816e-7 * (pow(10, (11.344 * (1 - 1/RATIO ))) - 1) ;
  RHS += 8.1328e-3 * (pow(10, (-3.49149 * (RATIO - 1))) - 1) ;
  RHS += log10(1013.246);
  // factor -3 is to adjust units - Vapor Pressure SVP * humidity
  double VP = pow(10, RHS - 3) * humidity;
  // (2) DEWPOINT = F(Vapor Pressure)
  double T = log(VP/0.61078); // temp var
  return (241.88 * T) / (17.558 - T);
}

//I2C callbacks

// callback for received command
void receiveData(int byteCount)
{
  while(Wire.available()) {
    number_command = Wire.read();

    if(number_command==TEMP) { //request temperature
      value_to_send = current_temperature;
    }
    if(number_command==HUMI) { //request humidity
      value_to_send = current_humidity;
    }

    if(number_command==PRES) { //request humidity
      value_to_send = current_pressure;
    }

    if(number_command==DEW) { //request dewpoint
     value_to_send = current_dewpoint;
    }
  }
}

// callback for sending data via I2C
void sendData()
{
  //convert the float values in a vector of 4 bytes to send via i2c bus
  char vector_to_send[4];
  memcpy(vector_to_send,(char*)&(value_to_send),4);
  Wire.write(vector_to_send,4);
}

 

Write the above skecth on the Arduino UNO and power on it….on the serial shell (and/or on the led display) you will see the vaues taken from the sensors.

Ok, let’s go ahead with the correspondant Raspberry PI code (at the moment it is implemented in an interactive way). It is a little more simple than the Arduino one (it is derived from the original one, but in thsi case I manage the transmission via I2C of float numbers, mapped each one on 4 bytes):

//ML meteo lab station v2.0 Raspi side
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>

// The PiWeather board i2c address
#define ADDRESS 0x04

//commands
#define TEMP 1
#define HUMI 2
#define PRES 3
#define DEW 4

// The I2C bus: This is for V2 pi's. For V1 Model B you need i2c-0
static const char *devName = "/dev/i2c-1";

int main(int argc, char** argv) 
{
  printf("I2C: Connecting\n");
  int file;

  if ((file = open(devName, O_RDWR))< 0) {
    fprintf(stderr, "I2C: Failed to access %d\n", devName);
    exit(1);
  }

  printf("I2C: acquiring buss to 0x%x\n", ADDRESS);

  if (ioctl(file, I2C_SLAVE, ADDRESS) < 0) {
    fprintf(stderr, "I2C: Failed to acquire bus access/talk to slave 0x%x\n", ADDRESS);
    exit(1);
  }

  int command;

  for (command= 1; command<=4; command++) {
    int val;
    unsigned char cmd[16];
    //printf("Sending %d\n", val);

    cmd[0] = command;
    if (write(file, cmd, 1) == 1) {

      // As we are not talking to direct hardware but a microcontroller we
      // need to wait a short while so that it can respond.
      //
      // 1ms seems to be enough but it depends on what workload it has
      usleep(10000);

      char buf[5];
      if (read(file, buf, 4) == 4) { //read 4 byte from i2c (a float -temp, humidity etc.-)
        float value_received;

      //convert 4 bytes received into a float
      memcpy((char*)&value_received,buf,4);
      //print the results
      if (command==TEMP)
        printf("Temperature (°C)= %f\n", value_received );
      if (command==HUMI)
        printf("Humidity (%)= %f\n", value_received );
      if (command==PRES)
        printf("Pressure (hPa)= %f\n", value_received );
      if (command==DEW)
        printf("Dew Point (°C)= %f\n", value_received );
     }
   }

   // Now wait else you could crash the arduino by sending requests too fast
   usleep(10000);
  }

  close(file);
  return (EXIT_SUCCESS);
}

Compile the Raspberry program with the same old gcc -o meteo_lab meteo_lab.c , then, if Arduino is already powered on, launch the program with ./meteo_lab

You will see on the Raspberry shell the current values for temperature, relative humidity, pressure and dew point temperature acquired (and calculated, in case of the dew point temperature) by our two sensors.

….So, you can monitor in each moment your lab weather condition! 🙂

Ok, I think you are thinking it is not too much….So, in the next posts we will see how to visualize these data (with a good webcam image on the side) on a web page accessed from internet… this is much more interesting or not? 😉

Bye bye geeks, c ya soon!

 

Advertisements

Arduino + Raspberry= Weather station with webcam (Part One: the I2C link)

Hi geek boys and girls!

After few more technical and obscure posts, finally I give some (original? I don’t think so…but it’s funny!) creative idea for your *ware open source projects.

With this post I would like to start building a open source meteo weather station.

In order to do this task I think you could connect together, such as a great cup of Mojito 🙂 :

  • A set of sensors (i.e. humidity, barometric pressure, temperature)
  • One little camera (i.e. we could start with an inexpensive webcam…if the weather conditions would permit its use!)
  • One Arduino UNO (or one Intel Galileo ;-))
  • One Raspberry PI Model B
  • One GSM module, or more simply, one less expensive USB internet key (to access via internet to your station)

Ok, first question: why Raspberry AND Arduino (and not only one board)?

Well… I would like to use Arduino to manage all sensors (especially if they would be analog sensors, since Raspberry doesn’t have analog inputs), and I would like to use Raspberry to manage the webcam and the communication with the external world via the GSM module (since it has  high-level functiona).

So I will use Arduino as acquisition board and Raspberry as data collector and as webserver.

Raspberry and Arduino can talk to each other using some different approaches, but I would like to use the I2C protocol, because it’s very simple and very well supported by Raspbian distribution and by native Arduino libraries.

I found this interesting post by Peter Mount in order to safely connect Arduino (as slave) and Raspberry (as master) using I2C.

In order to activate the I2C bus on Raspberry Peter reports five step on Raspbian:

  1. Open /etc/modprobe.d/raspi-blacklist.conf and comment the line reporting  i2c-bcm2708 (so, I2C is removed from blacklist)
  2. Add i2c-dev to the /etc/modules in order to activate the I2C driver at boot
  3. Install i2c-tools using thje well known apt-get install command
  4. Add the “pi” user to the i2c group (using the adduser pi i2c command) in order to let the user “pi” can access to I2C
  5. Reboot the Raspberry board.

In order to phisically connect Arduino and Raspberry via I2C:

  1. Connect SDA Raspberry pin (GPIO0) to SDA Arduino UNO pin (Digital IO 4)
  2. Connect SCL Raspberry pin (GPIO1) to SCL Arduino UNO pin (Digital IO 5)
  3. Connect the ground pins fo the two boards

Remember that Raspberry uses 3.3V as base voltage and Arduino UNO uses 5V…so pay attention: OR if you use a voltage converter in the I2C connection OR you are sure that you are using Arduino as slave I2C device and Raspberry as master I2C device. The first one choice is the best…but I love the risks (the risk in this cas is to have a “Raspberry flambé” 😉 ).

Ok, let’s go ahead. I used a little analog temperature sensor (the one provided in all starter kits by Analog Devices! 😉 ) connected to Arduino UNO (in the Analog input A0)  and then I modified the code provided on the Peter Mount blog in order to read the ambient temperature using Arduino and send it to Raspberry via I2C bus.

This is the simple circuit I used (zoom to better view the links):

20140411_084154

The code on Raspberry is the following (it’s directly taken from Peter Mount Blog, except for only one correction: I defined file as FILE* since declared as int raised a segmentation fault on fclose call….):

#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>

// The Arduino board i2c address
#define ADDRESS 0x04

//For V1 Model B you need i2c-0
//For V2 you need i2c-1(...in my case I have a V2 Raspberry Pi! :-) )
static const char *devName = "/dev/i2c-1"; //)

int main(int argc, char** argv) {
 if (argc == 1) {
   printf("Supply one or more commands to send to the Arduino\n");
   exit(1);
   }

 printf("I2C: Connecting\n");
 FILE* file;
 if ((file = open(devName, O_RDWR)) < 0) {
   fprintf(stderr, "I2C: Failed to access %d\n", devName);
   exit(1);
   }

 printf("I2C: acquiring buss to 0x%x\n", ADDRESS);
 if (ioctl(file, I2C_SLAVE, ADDRESS) < 0) {
   fprintf(stderr, "I2C: Failed to acquire bus access/talk to slave 0x%x\n", ADDRESS);
   exit(1);
   }

 int arg;
 for (arg = 1; arg < argc; arg++) {
   int val;
   unsigned char cmd[16];
   if (0 == sscanf(argv[arg], "%d", &val)) {
     fprintf(stderr, "Invalid parameter %d \"%s\"\n", arg, argv[arg]);
     exit(1);
     }

   printf("Sending %d\n", val);
   cmd[0] = val;
   if (write(file, cmd, 1) == 1) {
    // As we are not talking to direct hardware but a microcontroller we
    // need to wait a short while so that it can respond.
    // 1ms seems to be enough but it depends on what workload it has
    usleep(10000);
    char buf[1];
    if (read(file, buf, 1) == 1) {
      int temp = (int) buf[0];
      printf("Received %d\n", temp);
      }
   }

   // Now wait else you could crash the arduino by sending requests too fast
   usleep(10000);
  }
  fclose(file);
  return (EXIT_SUCCESS);
}

 

The code on Arduino UNO  is very simple and it is derived from the code taken from Peter Mount Blog:

#include <Wire.h>

#define SLAVE_ADDRESS 0x04

int number = 0; //command identifier (sent by Raspberry)
double temp; //variable used to store the temperature
const int sensorPin = A0; //pin where we read the temperature from the sensor
 
void setup() {
 // initialize i2c as slave
 Wire.begin(SLAVE_ADDRESS);
 // define callbacks for i2c 
 Wire.onReceive(receiveData);
 Wire.onRequest(sendData);
}
 
void loop() {
 delay(100);
 temp = GetTemp();
}
 
// callback for received data from I2C
void receiveData(int byteCount){
 while(Wire.available()) {
 number = Wire.read();
 
 //"2" is the command sent by Raspberry Pi in order to have the 
 // temperature as answer on I2C from Arduino
 if(number==2) { 
     number = (int)temp; //Arduino in this case sends the integer value of temperature
  }
 }
}
 
// callback for sending data on I2C
void sendData(){
 Wire.write(number);
}

float GetTemp(){
 // read the value on AnalogIn pin 0 
 // and store it in a variable
 int sensorVal = analogRead(sensorPin);

 // convert the ADC reading to voltage
 double voltage = (sensorVal/1024.0) * 5.0;
 
 // convert the voltage to temperature in degrees C
 // the sensor changes 10 mV per degree
 // the datasheet says there's a 500 mV offset
 // ((voltage - 500mV) times 100)
 double temperature = (voltage - .5) * 100;
 return temperature;
}

Ok… we can now download the cvode on Arduino and we can compile the client software on Raspberry Pi using the good old command:

gcc -o TakeTemp main.c

So, once powered on Arduino and verified using the Raspberry command

i2cdetect -y 1

that Arduino is correctly detected with I2C address 0x04, write in the Raspberry command line

./TakeTemp 2

Note that “2” is the I2C command used to receive from Arduino the acquired temperature (see the Arduino code above).

…If all will work correctly, you will read as output the integer value of the sensor temperature, acquired by Arduino and sent to Raspberry via I2C!

Yeah geek guys (and obviously geek gals)…”this is one small step for the human race”, but it’s also a good start to develop an open source meteo station. 😉

And now… a good relax!:-) That’s all folks (for this time)! 😉

…Bye bye!