Raspberry PI – Temperature and Humidity sensor

Welcome to a new post on what to “play” with the raspberry PI. This time the point is to query the pi how the weather and humidity was during the past days. Why do I need this for ? Well, wait for one of the next posts :). Right now let’s only focus on the “Temperature and Humidity” @ PI project.

I’ll start the other way around. This is what this project produces:
weather
(Btw, don’t trust the temperature peaks. No, it’s not that warm here ๐Ÿ˜ฆ It’s just that the sensor is placed in a non-optimal location).

First of all, the recognition part. Thanks to:
Gordons Projects – WiringPi
DHT Humidity Sensing on Raspberry Pi with GDocs Logging on Adafruit.com
Temp Sensor and wiringPi on jaRaspberryPi.Blog
and also to:
Creating Charts with GD::Graph LG
LibGD fonts and text functions
libGD Documentation from the Wayback Machine: archive.org
and:
Andrew’s Corner: Using Mutt with Gmail

What you need:
– A dht11 or dht22 sensor, wired as shown by Adafruit. I bought mine here, but I guess it exists also cheaper on eBay. I personally do however prefer Amazon :).
– A resistor (see Adafruit wiring). Mine is 6_point_something_K.

The project has 4 components:
– one component to measure the temperature & humidity
– one component to write the measured data + date and hour in a .csv file and to ensure that the file does not grow larger than n days. In my case this component runs every hour from cron.
– one component to react to the incoming mail requesting for the weather chart and to replay with the current chart as attachment.
– and last but not least, one component to generate the chart (see the image above) based on the data from the CSV.

1. Measure the temperature and humidity
This part is compiled based on the sources I found in the 2 liks above: on Adafruit.com and on jaRaspberryPi.Blog
Below is my version; As I learned from the feedback I got that the pasted sources are not copy/paste friendly, I try to improve the format for the code using the CodeHTMLer
The program tries to measure 5 times a value every minute and then reports back their average. In case a value was not correctly read, it retries after 4 seconds, no more than 10 times. And finally, in case no attempt worked (difficult to concieve, unless the sensor got disconnected), it returns 100, 100 as temperature and humidity. This exceptional case would then have to be checked for by the other involved components.

License: Please notice the license governing the source code below in the “License & About” page.

#include <wiringPi.h>

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#define MAXTIMINGS 85
#define DHTPIN 7
int dht11_dat[5] = {0,0,0,0,0};
float retValues[2] = {-1,-1};

void read_dht11_dat()
{
  uint8_t laststate = HIGH;
  uint8_t counter = 0;
  uint8_t j = 0, i;
  float ff; // fahrenheit
  float f,h;

  retValues[0] = -1;
  retValues[1] = -1;

  dht11_dat[0] = dht11_dat[1] = dht11_dat[2] = dht11_dat[3] = dht11_dat[4] = 0;

  // pull pin down for 18 milliseconds
  pinMode(DHTPIN, OUTPUT);
  digitalWrite(DHTPIN, LOW);
  delay(18);
///  digitalWrite(DHTPIN, HIGH);
///  delay(500);
  
  // then pull it up for 40 microseconds
  digitalWrite(DHTPIN, HIGH);
  delayMicroseconds(40); 
///  digitalWrite(DHTPIN, LOW);
///  delay(20);

  // prepare to read the pin
  pinMode(DHTPIN, INPUT);

  // detect change and read data
  for ( i=0; i< MAXTIMINGS; i++) {
    counter = 0;
    while (digitalRead(DHTPIN) == laststate) {
      counter++;
      delayMicroseconds(1);
      if (counter == 255) {
        break;
      }
    }
    laststate = digitalRead(DHTPIN);

    if (counter == 255) break;

    // ignore first 3 transitions
    if ((i >= 4) && (i%2 == 0)) {
      // shove each bit into the storage bytes
      dht11_dat[j/8] <<= 1;
      if (counter > 16)
        dht11_dat[j/8] |= 1;
      j++;
    }
  }

  // check we read 40 bits (8bit x 5 ) + verify checksum in the last byte
  // print it out if data is good
  if ((j >= 40) && 
      (dht11_dat[4] == ((dht11_dat[0] + dht11_dat[1] + dht11_dat[2] + dht11_dat[3]) & 0xFF)) ) {

    h = dht11_dat[0] * 256 + dht11_dat[1];
    h /= 10;
    f = (dht11_dat[2] & 0x7F)* 256 + dht11_dat[3];
        f /= 10.0;
        if (dht11_dat[2] & 0x80) f *= -1;
    ff = f * 9. / 5. + 32;
    retValues[0] = f;
    retValues[1] = h;
//1
//    printf("Temp = %.1f *C / %.lf *F , Hum = %.1f \%\n", f, ff, h);
//    f = dht11_dat[2] * 9. / 5. + 32;
//    printf("Humidity = %d.%d %% Temperature = %d.%d *C (%.1f *F)\n", 
//            dht11_dat[0], dht11_dat[1], dht11_dat[2], dht11_dat[3], f);
  }
  else
  {
//2
//    printf("Data not good, skip\n");
  }
}

int main (void)
{

  if (wiringPiSetup () == -1) {
     printf("100,100\n");
     exit (1) ;
  }
  float tmp;
  float hum;
  int i;
  int wrongCounter;
  float rightCounter;
  rightCounter = 0.0;

  for (i=0;i<5;i++) {
     wrongCounter = 0;
     do {
        wrongCounter++;
         read_dht11_dat();
    delay(4000);
     } while (retValues[0] == -1 && wrongCounter < 10);
     if (retValues[0] != -1) {
        rightCounter += 1.0;;
        tmp += retValues[0];
        hum += retValues[1];
     }
     delay(60000); //delay 1 minute
  }

if (rightCounter > 0) {
  printf("%.1f,%.1f\n", tmp / rightCounter , hum / rightCounter);
} else {
  printf("100,100\n");
}
  return 0 ;
}


After installing wiringPI libraries, the source above can be built via:
gcc -o dht22 dht22.c -L/usr/local/lib -lwiringPi

2. Handling the csv files and calling the program above via Bash script

License: Please notice the license governing the source code below in the “License & About” page.

#!/bin/bash

dhtTime=$(date +"%Y%m%d,%H")
dhtFile="/home/pi/Shared/dht.csv"
dhtFileAll="/home/pi/Shared/dhtAll.csv"
dayNow=$(date +"%Y%m%d")
relevantDays=7 #This means we save the current day and n days before


cat $dhtFile | grep $dayNow > /dev/null
if [ $? -ne 0 ]; then
    #  echo "New Day"
    tmpFile="/var/tmp/"$dayNow".dht"
    regEx=""
    for i in `seq $relevantDays`
    do
    if [ $i -ne "1" ]; then
    regEx=$regEx"\|"
    fi
    calcDate=`(date --date "$dayNow -$i days" +"%Y%m%d")`
    regEx=$regEx""$calcDate
    done
    regExBack="/"$regEx"/d"
    cp $dhtFile $tmpFile
    sed  -i.bak $regExBack $tmpFile
    cat $tmpFile >> $dhtFileAll
    rm $tmpFile
    rm $tmpFile".bak"

    regExKeep="/"$regEx"/!d"
    #cp $dhtFile $tmpFile
    sed  -i.bak $regExKeep $dhtFile
    #rm $tmpFile  
    rm $dhtFile".bak"
fi


echo -n $dhtTime"," >> $dhtFile
sudo /home/pi/dev/dht/dht22 >> $dhtFile


This script saves the current measurements in dht.csv, it makes sure that no more than the values for the past “relevantDays” are present in the csv, and stores the older values in dhtAll.csv just in case anyone is curious how the weather was on n-th of April 2013.

3. React to the incoming mail and answer with the chart as attachment
As I shown you earlier, we use fetchmail to check for eMails on gmail, but in order to also send attachments via eMail we cannot use ssmtp anylonger. Instead, you need to install “mutt” (see details here).
In order to make mutt work, a file like: $HOME/.muttrc is needed. For me it worked easier than the example in the linked page. It’s like:
set smtp_url = "smtp://_you_@smtp.gmail.com:587/"
set smtp_pass = "_your pass_"
set from = "_you_@gmail.com"
set realname = "_your Name_"

Further more this code must be entered in the parser.sh script
a. Once to check for the incoming mail:
echo $task | grep "weather" > /dev/null
if [ $? -eq 0 ]; then
sendWeather $sender
fi

b. And once to react and send the replay:
sendWeather() {
/home/pi/dev/dht/charter
echo "The Weather over past days" | mutt -s "Weather Chart" -a "/home/pi/Shared/ram/weather.png" -- $1
rm /home/pi/Shared/ram/weather.png
}

Please check my previous post: Raspberry Pi โ€“ Send and receive (g)Mail for details on how to receive and send Gmails with the PI.

4. Drawing the chart
For the chart, the libgd and libpng are needed.
It can be built as follows:
gcc -o charter charter.c -lgd -lpng
and the code is below.

License: Please notice the license governing the source code below in the “License & About” page.

#include "gd.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include "gdfontg.h"


#define OUTPUT "/home/pi/Shared/ram/weather.png"
#define CSV "/home/pi/Shared/dht.csv"
#define MAXDAYS 10
#define MARGINL 100
#define MARGINR 100
#define MARGINB 100
#define MARGINT 50
#define HEIGHT 450
#define DELTA 5
#define HORIZONTALLINES 5

char dateStr[MAXDAYS][10];
char dateHStr[24 * MAXDAYS][12];
int  countValues;
float  tValues[24 * MAXDAYS];
float  hValues[24 * MAXDAYS];
int  countDays;
int  countHDays;
float ttop,tlow,htop,hlow;

void parseCSV(FILE *csv) {

char line[40];
char v1[10], v2[10], v3[10], v4[10],v5[10];

char lastDateStr[10];
char lastDateHStr[12];
countDays = -1;
countHDays = -1;
countValues = 0;

strcpy(lastDateStr,"");strcpy(lastDateHStr,"");

ttop=-100;tlow=100;htop=-10;hlow=110;

while ( fgets (line, sizeof line, csv ) != NULL ) /* read a line */
{
  strcpy(v1,"");strcpy(v2,"");strcpy(v3,"");strcpy(v4,"");strcpy(v5,"");
  strcpy(v1, strtok (line,","));
  strcpy(v2, strtok (NULL,","));
  strcpy(v3, strtok (NULL,","));
  strcpy(v4, strtok (NULL,","));
  //printf("%s -- %s -- %s -- %s",v1,v2,v3,v4);
  if (strcmp(v4,"100\n") == 0) continue; //there was an erroneous write in the DB; ignore it
  strcpy(v5,v1);
  strcat(v5,"-");
  strcat(v5,v2);
  if (strcmp(lastDateStr,v1) != 0) {
    countDays++;
    if (countDays >= MAXDAYS) return;
    strcpy(dateStr[countDays],v1);
    strcpy(lastDateStr,v1);  
  }
  float t,h;
  t = atof(v3);
  h = atof(v4);
  if (h == 0.0) continue; //couldn't parse; ignore the values 
  if (strcmp(lastDateHStr,v5) != 0) {
      if (countHDays > -1) {
          if (countValues > 1) {
              tValues[countHDays] = tValues[countHDays] / countValues;
              hValues[countHDays] = hValues[countHDays] / countValues;
          }
          if (tValues[countHDays] < tlow) tlow = tValues[countHDays];
          if (tValues[countHDays] > ttop) ttop = tValues[countHDays];
          if (hValues[countHDays] < hlow) hlow = hValues[countHDays];
          if (hValues[countHDays] > htop) htop = hValues[countHDays];
      }
      countHDays++;
      strcpy(dateHStr[countHDays],v5);
      strcpy(lastDateHStr,v5);
      countValues=1;
      tValues[countHDays]=t;
      hValues[countHDays]=h;
  } else {
      countValues++;
      tValues[countHDays]+=t;
      hValues[countHDays]+=h;  
  }
}

//take care of the last values:
if (countValues > 1) {
    tValues[countHDays] = tValues[countHDays] / countValues;
        hValues[countHDays] = hValues[countHDays] / countValues;
}
if (tValues[countHDays] < tlow) tlow = tValues[countHDays];
if (tValues[countHDays] > ttop) ttop = tValues[countHDays];
if (hValues[countHDays] < hlow) hlow = hValues[countHDays];
if (hValues[countHDays] > htop) htop = hValues[countHDays];




/*int i;
for (i=0;i<=countHDays;i++) {
    printf("%s==%.2f==%.2f\n",dateHStr[i],tValues[i],hValues[i]);
}*/
//printf("%.2f==%.2f==%.2f==%.2f\n",tlow,ttop,hlow,htop);
}



int main()
{

FILE *csv = fopen ( CSV, "r" );

if (csv != NULL) {
  parseCSV(csv);
  fclose(csv);
} else {
  return 0;
}

gdImagePtr im;
FILE *fp;
//int cor_rad = 60;
int width = DELTA*countHDays+MARGINL+MARGINR;
int height = HEIGHT+MARGINB+MARGINT;
//printf("%d, %d",width,height);
im = gdImageCreateTrueColor(width, height);
//printf("Created");
gdImageFilledRectangle(im, 0, 0, width-1, height-1, 0x00FFFFFF);

int black = gdImageColorAllocate(im, 0, 0, 0);
int white = gdImageColorAllocate(im, 255, 255, 255);
int darkRed = gdImageColorAllocate(im, 180, 0, 0);
int darkBlue = gdImageColorAllocate(im, 0, 0, 180);

gdImageFilledRectangle(im, MARGINL-3, MARGINT,MARGINL,HEIGHT+MARGINT, black);
gdImageFilledRectangle(im, MARGINL-3, MARGINT+HEIGHT,MARGINL+DELTA*countHDays,HEIGHT+MARGINT+3, black);
gdImageFilledRectangle(im, width-MARGINR, MARGINT,width-MARGINR+3,HEIGHT+MARGINT+3, black);


int starty=MARGINT+0.05*HEIGHT;

gdFontPtr marginFont = gdFontGetGiant();
char tmpStr[14];
sprintf(tmpStr,"%.2f *C",ttop);
gdImageString(im, marginFont, MARGINL / 2 - (strlen(tmpStr) * (marginFont)->w / 2), starty - marginFont->h / 2, tmpStr, darkRed);
sprintf(tmpStr,"%.2f *C",tlow);
gdImageString(im, marginFont, MARGINL / 2 - (strlen(tmpStr) * (marginFont)->w / 2), starty + 0.9*HEIGHT - marginFont->h / 2, tmpStr, darkRed);

sprintf(tmpStr,"%.2f%%",htop);
gdImageString(im, marginFont, width - MARGINR / 2 - (strlen(tmpStr) * (marginFont)->w / 2), starty - marginFont->h / 2, tmpStr, darkBlue);
sprintf(tmpStr,"%.2f%%",hlow);
gdImageString(im, marginFont, width - MARGINR / 2 - (strlen(tmpStr) * (marginFont)->w / 2), starty + 0.9*HEIGHT - marginFont->h / 2, tmpStr, darkBlue);


int deltaLine = HEIGHT / (HORIZONTALLINES+1);
int i;
int lineY;
float valY;
float tDelta=ttop-tlow;
float hDelta=htop-hlow;

for (i=0;i<HORIZONTALLINES;i++) {
    lineY=(i+1)*deltaLine+MARGINT;
    gdImageLine(im, MARGINL, lineY,MARGINL+DELTA*countHDays,lineY, black);
    valY = ttop-((19.0-HORIZONTALLINES)/(18*HORIZONTALLINES+18))*tDelta-(10*tDelta/(9*HORIZONTALLINES+9))*i;
    sprintf(tmpStr,"%.2f *C",valY);
    gdImageString(im, marginFont, MARGINL / 2 - (strlen(tmpStr) * (marginFont)->w / 2), lineY - marginFont->h / 2, tmpStr, darkRed);
    valY = htop-((19.0-HORIZONTALLINES)/(18*HORIZONTALLINES+18))*hDelta-(10*hDelta/(9*HORIZONTALLINES+9))*i;
    sprintf(tmpStr,"%.2f%%",valY);
    gdImageString(im, marginFont, width - MARGINR / 2 - (strlen(tmpStr) * (marginFont)->w / 2), lineY - marginFont->h / 2, tmpStr, darkBlue);
}

float dy=(0.9*HEIGHT)/tDelta;
float dhy=(0.9*HEIGHT)/hDelta;

int x,y,lastX,lastY,lastXDay;
int hx, hy, lastHX, lastHY;
char lastDay[10]="";
lastXDay = MARGINL;

for (i=0;i<=countHDays;i++) {
    y=starty+round((ttop-tValues[i])*dy);
    x=MARGINL+i*DELTA;
    
    hy=starty+round((htop-hValues[i])*dhy);
    hx=MARGINL+i*DELTA;
    
    if (strncmp(lastDay, dateHStr[i],8) == 0) {
        gdImageFilledEllipse(im, x, y, 5, 5, darkRed);
        gdImageFilledEllipse(im, hx, hy, 5, 5, darkBlue);
    } else {
        gdImageFilledEllipse(im, x, y, 9, 9, black);
        gdImageFilledEllipse(im, hx, hy, 9, 9, black);
        if (i > 0) {
            gdImageString(im, marginFont, (lastXDay + x) / 2 - (strlen(lastDay) * (marginFont)->w / 2), (height - MARGINB/2) - marginFont->h / 2, lastDay, black);
            lastXDay = x;
            gdImageLine(im, x, MARGINT+HEIGHT-6,x,MARGINT+HEIGHT+9, black);
        }        
        strncpy(lastDay, dateHStr[i], 8);
        lastDay[8]=0;
    }
    if (i != 0) {
        gdImageLine(im, lastX, lastY, x, y, darkRed);
        gdImageLine(im, lastHX, lastHY, hx, hy, darkBlue);
    }
    lastX = x; lastY = y;
    lastHX = hx; lastHY = hy;
}

gdImageString(im, marginFont, (lastXDay + x) / 2 - (strlen(lastDay) * (marginFont)->w / 2), (height - MARGINB/2) - marginFont->h / 2, lastDay, black);

strcpy(tmpStr,"Temperature");
gdImageString(im, marginFont, MARGINL+100 - (strlen(tmpStr) * (marginFont)->w / 2), MARGINT/2 - marginFont->h / 2, tmpStr, darkRed);
strcpy(tmpStr,"Humidity");
gdImageString(im, marginFont, width-MARGINR-100 - (strlen(tmpStr) * (marginFont)->w / 2), MARGINT/2 - marginFont->h / 2, tmpStr, darkBlue);


fp = fopen(OUTPUT, "wb");
if (!fp) {
    printf("err");
    gdImageDestroy(im);
    return 0;
}
gdImagePng(im, fp);
fclose(fp);

gdImageDestroy(im);
return 0;
}


Enjoy ! ๐Ÿ™‚

For questions and discussion please use the Raspberry PI forum.

Tagged with: , , , , , , ,
Posted in Raspberry PI