Thursday, February 26, 2015

Using the AM2315 Temperature/Humidity Sensor

This post is a continuation of my series on the weather station that I built.  On e of the sensors that it uses is the AM2315 temperature/humidity sensor

The AM2315 is very poorly documented.  This leads to several problems when attempting to use it. Thankfully, Sopwith has provided details of his experience with this device http://sopwith.ismellsmoke.net/wp-content/uploads/2014/03/PI-How-To-AM2315.pdf so we can move on more quickly to making use of it. Sopwith provides a more detailed explanation of how to set up and use this device. If you are using Python, then you must read his article, since he explains some additional problems caused by the way the Python library works.

The first problem you will run into is that it doesn't show up on the i2c bus. That is because the device stays in sleep mode until it is woken up to prevent generating heat that would affect the humidity sensor accuracy. If you run the command i2cdetect twice quickly, then the second time it will show up.

The second problem is that the i2c address is wrong in the datasheet. It is no longet at 0xB8, but is instead at 0x5c. This is not a big problem since you will see the correct address show up in the second i2cdetect results.

The third problem is really a side effect of the first - the device does not keep updated values in registers like most i2c devices. You will have to issue a read request command and then read the response.

Even though I use the i2c support provided by the WiringPi library, it is best to do raw reads and writes to the device because it does not follow the i2c standard very well. This is the sequence to follow:

  • Write a zero byte to it twice to wake it up
  • Write a read request to make it update the temperature and humidity values
  • Read the response to the read request
  • Do some manipulations on the data to get the values in degress Celcius and %RH


Here is an example program in C with comments that give more detailed explanation.
/*
    example code to test am2315 (temp/humid sensor) on i2c bus
 ted.b.hale@gmail.com
*/

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdint.h>
#include <wiringPi.h>
#include <wiringPiI2C.h>

int main(int argc, char *argv[])
{
 int n, fd; 
 
 // read request - 3 is the read register command
 // 0 is the address to start at
 // 4 is the number of bytes to read
 unsigned char read_request[3] = {3, 0, 4};
 
 // buffer for the response: command byte, length byte, 4 bytes data, 2 bytes of checksum
 unsigned char response[8];
 
 // dummy data sent to wake up device
 unsigned char dummy[1] = {0};
 
 // the final results 
 float humidity, celsius;
 
 // open the am2315 device using WiringPi
 // 0x5C is bus address of am2315
 // fd is the "file descriptor" used in later read and writes
 fd = wiringPiI2CSetup(0x5c);  
 if (fd==-1)
 {
  printf("wiringPiI2CSetup failed\n");
  return 0;
 }
 
 // run until killed with Ctrl-C
 while (1)
 {
  // send some data to wake it up
  n = write(fd, dummy, 1);
  n = write(fd, dummy, 1);
  
  // send the read request
  n = write(fd, read_request, 3);
  printf("write returned %d bytes\n",n);
  
  // very short delay to allow device to do data conversion
  delay(2);
  
  // read the reaponse
  n = read(fd, response, 8);
  printf("read returned %d bytes\n",n);
  
  // sanity check on data returned
  // first byte should echo the read requst byte (3)
  // second byte should indicate 4 bytes of data returned
  // I don't bother verifying the checksum
  if ((response[0]!=3) || (response[1]!=4))
  {
   printf("i2c response invalid\n");
   for (n=0; n<8; n++)
    printf("%02x ",response[n]);
  }
  else 
  {
   // (high byte * 256) + low byte
   // divide by 10 
   humidity = (256*response[2] + response[3])/10.0;

   // same as above but mask out the sign bit on the high byte
   celsius = (256 * (response[4] & 0x7F) + response[5]) / 10.0;
   // make result negative if the sign bit is set
   if ((response[4]&0x80)!=0)
    celsius *= -1.0;

   printf("   humidity = %5.1f%%\n",humidity);
   printf("temperature = %5.1f\n",celsius);
  }
  printf("\n\n\n");

  // wait two second and loop again
  delay(2000);
 }
  
 return 0 ;
}
The AM2315 is supposed to be a very accurate device, but after using it a while, I am very suspicious of the humidity values.  The plots of my data show the humidity doing things that are not possible. I suspect that it sometimes gets saturated with moisture from fog, dew or rain and then it reads 100% for a long time. Mine is mounted so that it is directly exposed to the outside environment. Best practice says that I should have a radiation shield protecting the temperature sensor from direct sunlight. I plan on creating one for the AM2315 and this may also allow the humidity sensor to be more accurate.

Friday, February 6, 2015

Log Data to the Weather Underground

The next topic for my weather station system is how to send your data to a "Personal Weather Station" on the Weather Underground web site. This provides a nice interface for the world to see your current (and past) conditions.

The PWS Overview explains what personal weather stations are. It also has a buying guide. For comparison, the total cost for my system was somewhere around $150 (That's just a very rough guess.)

See PWS Upload Protocol for the complete documentation for uploading data for a PWS. To summarize, you need to send a properly formatted string to a web address (a simple HTTP GET to a PHP script.)

So here is a simplified example of the code that I am using.

/***********************************************************************
   Filename:   wunderground.c
   send current conditions to my Personal Weather Station on 
   The Weather Underground
   
   Uses libcurl to send the data via HTTP

   build with:
   gcc -o wutest wunderground.c -L/usr/local/lib -L/usr/local/ -lcurl -lwiringPi

  23-Jan-2015   Ted Hale  created this as an example for blog

************************************************************************/

/* system includes */
#include <stdio.h>
#include <stdlib.h>  
#include <stdarg.h>
#include <time.h>
#include <string.h>
#include <curl/curl.h>

#define TRUE 1
#define FALSE 0

// these are normally global varables that my weather system updates
// just put them here for this example
float outsideTemp;
float windSpeed;
float windGust;
float rainPeriod;
float humidity;
float barometric;

// my PWS ID and password
char *myStationID = "KVAWILLI99";
char *myStationPassword = "NOTMYPASSWORD";

// structure used by the libcurl write callback function
struct url_data {
    size_t size;
    char* data;
};
 
//=====================================================================
// write callback function needed by libCurl
size_t write_data(void *ptr, size_t size, size_t nmemb, struct url_data *data) {
    size_t index = data->size;
    size_t n = (size * nmemb);
    char* tmp;

    data->size += (size * nmemb);
    tmp = realloc(data->data, data->size + 1); /* +1 for null terminator */

    if(tmp) {
        data->data = tmp;
    } else {
        if(data->data) {
            free(data->data);
        }
        printf("skynet_post Failed to allocate memory.\n");
        return 0;
    }

    memcpy((data->data + index), ptr, n);
    data->data[data->size] = '\0';

    return size * nmemb;
}

//=====================================================================
// upload current conditions to Weather Underground
int UpdateWunderground()
{
 // URL format 
 char *myFmt = "http://weatherstation.wunderground.com/weatherstation/updateweatherstation.php?"
     //   1           2           3    4    5    6       7       8  
  "ID=%s&PASSWORD=%s&dateutc=%04d-%02d-%02d+%02d%%3A%02d%%3A%02d"
  //              9             10       11        12
  "&windspeedmph=%f&windgustmph=%f&tempf=%f&rainin=%f"
  //        13          14
  "&baromin=%f&humidity=%f&action=updateraw";

 /* 1 ID
  * 2 passwd
  * 3 yr
  * 4 mon
  * 5 day
  * 6 hr
  * 7 min
  * 8 sec
  * 9 winspeed
  * 10 gusts
  * 11 Outdoor temp
  * 12 rain
  * 13 baro 
  * 14 humidity
  */

 int   error = TRUE;
 time_t   now;
 struct tm  *dt;
 int   hour,minute,second,year,month,day;
 char   url[1024];
 
 CURL  *curl;
 CURLcode res;
 struct url_data response;
 
 time(&now);
 dt = gmtime(&now);
                         
 // build the URL string
 //                                      1              2
 snprintf(url, sizeof(url)-1, myFmt, myStationID, myStationPassword,
 //         3          4          5           6           7          8
  dt->tm_year, dt->tm_mon, dt->tm_mday, dt->tm_hour, dt->tm_min, dt->tm_sec,
 //    9           10         11        12             13        14
  windSpeed, windGust, outsideTemp, rainPeriod, barometric, humidity);

  // guarantee null termination of string
 url[sizeof(url)-1] = 0;
  
 curl = curl_easy_init();
 if (curl) {
  response.size = 0;
  response.data = malloc(4096); /* reasonable size initial buffer */ 
  response.data[0] = '\0';
  curl_easy_setopt(curl, CURLOPT_URL, url);
  curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET");
  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
  curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
  res = curl_easy_perform(curl);
  if(res != CURLE_OK)
  {
   printf("curl_easy_perform() failed: %s\n",curl_easy_strerror(res));
   error = TRUE;
  } else {
   error = (strcmp(response.data,"Success") != 0);
  }
  curl_easy_cleanup(curl); 
  free (response.data);
 } else {
  printf("curl_easy_init failed\n");
  error = TRUE;
 }
 
 return error;
}

int main()
{
 // set some dummy data
 outsideTemp = 69.7;
 windSpeed = 2.3;
 windGust = 5.6;
 rainPeriod = 0.001;
 humidity = 75.0;
 barometric = 31.5;

 UpdateWunderground();
 
 return 0;
}

Please let me know if you use this code.  I would like to hear how it works for you.

Friday, January 23, 2015

GPIO Interrupts using WiringPi

My weather station has two devices that send simple pulses to the Raspberry Pi. The rain gauge pulses each time its "bucket" fills and the wind speed gauge pulses for each rotation. The best way to handle this is by using interrupts.

An interrupt is a signal to the computer to stop what it is doing and do something else.  These are used extensively by the operating system, but an application can use them as well.  If you have a GPIO input that needs to be responded to, you could poll the GPIO pin in a loop waiting for it to change, but this can be unreliable for very brief state changes. It is also wasteful of the CPU.  The better way is to assign an interrupt that will be activated when the GPIO pin changes state.

I code all my projects in C and use Gordon's WiringPi API.  This library makes it much easier to use GPIO.  Interrupts are now supported using the wiringPiISR function.  (The waitForInterrupt function is deprecated and should not be used.) Using interrupts takes only three steps:

  • Create a function to be called when the GPIO pin changes state. This must be defined like this: void myInterrupt(void)
  • Initialize the WiringPi library by calling wiringPiSetup
  • Call wiringPiISR to configure the interrupt

You can choose to have your interrupt called when the pin goes low, goes high, or both (INT_EDGE_FALLING, INT_EDGE_RISING, INT_EDGE_BOTH)

My interrupts simply increment a global variable. The value of this variable is then checked periodically (typically once per second) and the count is converted into the appropriate units for the device.

An example of how to use an interrupt is shown below.

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <wiringPi.h>

// Use GPIO Pin 17, which is Pin 0 for wiringPi library

#define BUTTON_PIN 0

// the event counter 
volatile int eventCounter = 0;

// -------------------------------------------------------------------------

void myInterrupt(void) {
   eventCounter++;
}

// -------------------------------------------------------------------------

int main(void) {
  // sets up the wiringPi library
  if (wiringPiSetup () < 0) {
      fprintf (stderr, "Unable to setup wiringPi: %s\n", strerror (errno));
      return 1;
  }

  // set Pin 17/0 generate an interrupt on high-to-low transitions
  // and attach myInterrupt() to the interrupt
  if ( wiringPiISR (BUTTON_PIN, INT_EDGE_FALLING, &myInterrupt) < 0 ) {
      fprintf (stderr, "Unable to setup ISR: %s\n", strerror (errno));
      return 1;
  }

  // display counter value every second.
  while ( 1 ) {
    printf( "%d\n", eventCounter );
    eventCounter = 0;
    delay( 1000 ); // wait 1 second
  }

  return 0;
}

All of the code from my weather station can be found at
https://code.google.com/p/raspberry-pi-hobbyist/source/browse/

You can also get it using git:
git clone https://ted.b.hale@code.google.com/p/raspberry-pi-hobbyist/

Sunday, December 28, 2014

Raspberry Pi Weather Station

My latest Pi project involves creating a weather station in a way very different from most others. I will cover various parts of this project in my next several posts.
The weather station completed and mounted outdoors.
The  most common way that a Raspberry Pi (or any other computer) is interfaced is via a pre-built system that has a serial or USB connection. This is the fastest and easiest way to do this. But what fun is doing things the way everyone else does?

I had an old weather station that was relatively cheap (less than $100) and did not have any drivers for Linux. I ran it on an old PC for several years, but it finally stopped working. The wireless part was never very reliable and it was a pain to keep replacing batteries. I was able to salvage the anemometer (wind speed) and rain gauge and hack them into my new system.

For temperature and humidity readings I use the AM2315 from adafruit. For barometric pressure I use the MPL115A2, also from adafruit. Both of the devices interface via the I2C bus. I have to give a plug for adafruit. It's a great place to get parts to connect to the Raspberry Pi.

The interface board and Pi on a mounting plate.
The anemometer and rain gauge are simple contact closure interfaces. Each tiny bucket of rain and each rotation of the anemometer produce a single pulse. These are connected simply through GPIO pins and drive interrupts which count the pulses.

The interface board that connects to the Pi is actually very simple. It mostly just passes GPIO pins to screw terminals. The MPL115A2 is the tiny blue board next to the ribbon connector.





Adafruit also provided the perfect weatherproof enclosure. I will have to drill holes to feed wires in, but those will be sealed with silicone.








In the near future I will write up posts on some of the problems I had to overcome to complete this project.
  • Handling GPIO interrupts
  • Interfacing to the AM2315 via I2C
  • Interfacing to the MPL115A2 via I2C
  • Calibrating the rain and wind sensors
  • Logging to my MySQL database server
  • Logging to Weather Underground
  • Providing a nice web interface to display the weather data
Also, I will post my source code for others to use and/or learn from.

This has been a very fun project and I expect to use the weather station for many years to come.

Of course, Murphy had to strike - when I went to mount the system outdoors, it was raining!


Sunday, December 14, 2014

My New System Checklist

I recently had need to create a new Raspbian system for a project and decided to record all the things I did after the system image first boots. Frequently, I forget one or two of these, so this will become a checklist that I follow when I create a new system.

There are many guides to creating a system for the Raspberry Pi and this post is not an attempt to create another. I am putting this here for my own reference as much as to share.  To make it helpful to beginners, I have added some explanations.  You may prefer nano over vi as the file editor.

Please feel free to use the comments section to let people know what customization you like to make for your Pi systems.

Load image as usual
raspi-config runs first time
- expand file system
- Internationalization -> set locale -> TZ = US-Eastern
- Internationalization -> Keyboard = English(US)
- advanced -> hostname (RasPi-##-Purpose)
- advanced -> mem split 16 for GPU
- advanced -> enable SPI and I2C and Serial
reboot, and log in as pi (raspberry)

CPU overclocking would also be set up in raspi-config, but I haven't had any need to do this.

All of the following commands require root privilege.  You can either put sudo before each command or enter sudo -i and run a shell as root.

Create a new user for myself, give it sudoer privilege.
adduser ted 
echo "ted ALL=(ALL) NOPASSWD: ALL" >>/etc/sudoers

Update the package database and upgrade all installed packages.
apt-get update 
apt-get upgrade

Install some new packages.
apt-get install samba screen libmysqlclient-dev libi2c-dev

Configure Samba (Windows file sharing)
vi /etc/samba/smb.conf
uncomment "socket options = TCP_NODELAY"
delete all shares and add:
[opt]
   comment = opt
   writable = yes
   locking = no
   path = /opt
   public = yes

Restart the Samba service
service samba restart  

Edit the SSH server config.  Turning off DNS reverse lookups will speed up the connection process when to log in through SSH.
vi /etc/ssh/sshd_config
add "UseDNS no"

Edit the netwrok configuration and set static IP address and wifi config.  The interface name for the wifi will be used below in the supplicant file.
vi /etc/network/interfaces
iface eth0 inet static
   address 192.168.0.51
   netmask 255.255.255.0
   gateway 192.168.0.1
   
iface home inet static 
   address 192.168.0.53
   netmask 255.255.255.0
   gateway 192.168.0.1

Edit the wifi supplicant file.  The "id_str" setting connects back to the name used above.
vi /etc/wpa_supplicant/wpa_supplicant.conf
network={
        id_str="home"
        ssid="NOTMYSSID"
        psk="NotMyPassword"
        proto=WPA
        key_mgmt=WPA-PSK
        pairwise=TKIP
        auth_alg=OPEN
}

Install Gordon's WiringPi library.  I use this extensively in my C programming.
cd ~
git clone git://git.drogon.net/wiringPi
cd wiringPi
git pull origin
./build
gpio -v
gpio readall

Edit the kernel module configuration to enable SPI, I2C, and 1-Wire.
vi /etc/modprobe.d/raspi-blacklist.conf
uncomment SPI and I2C devices

vi /etc/modules
add this
# SPI devices  
spi-dev  
# I2C devices  
i2c-dev  
i2c_bcm2708
# 1-Wire devices  
w1-gpio  
# 1-Wire thermometer devices  
w1-therm  

Finally, reboot the system again.  Then log on as the new user you created and  remove the default user.
userdel pi

If you don't do this last step and your system is accessible from the internet, then it will not be long (sometimes only hours or minutes) before a hacker finds it and does bad things.  My firewall log shows constant attempts to brute force a login via SSH and "pi" is a common user name that is tried.



Tuesday, December 9, 2014

Wifi Router Case Mod

In my previous post I showed how to use the case from an old LinkSys router as a case for a Raspberry Pi.  Today I decided that it needed a little improvement.  Bring on the blinking lights.  Isn't everything better with blinking lights?

I showed in the posts Server Box with Utilization Displays and CPU and I/O Utilization Display - Details how to use LEDs for a utilization display.  This project is a little smaller scale - only six LEDs instead of twenty.



The circuit is very simple.  The positive lead to each LED is connected to a resistor.  All of the resistors are connected to 5V.  The negative lead of each LED is connected to a GPIO pin.  A low signal on the GPIO pin turns the LED on.

This inverts the logic, but that can be handled in software.  This has the benefit of being able to push more current through the LEDs than would be possible if the GPIO line was connected to the positive lead of the LED.


This PCB layout shows a close approximation of how I made the circuit.  I didn't make a printed circuit board, but I have been designing some for work this week, so I did this drawing using PCB Artist.  I definitely plan to create some PCBs for my Pi hobby, once I decide what to make next.


All the wires are connected directly to a ribbon cable. I created a real power plug while I was at it.

The LED circuit is pushed into the front part of the case and through some holes that I drilled.  It is held in place by friction and a little tape.






My latest Raspberry Pi creation is stacked with the modem and router with all their blinking lights.

Now maybe it will not feel inadequate.



Sunday, December 7, 2014

Raspberry Pi Case From Wifi Router


You have most likely seen one of these somewhere before.

This case style is very common.  If you happen to get your hands on one, you can make a great case for the Raspberry Pi.

(Or, you can re-load the router firmware.  See dd-wrt.org for more information.)

I had two in my huge pile of junk, so I though I'd have some fun. This was literally a ten minute project.


The case pops apart easily, if you know the trick.  Grab the blue front part and pull apart from the rear part.  After that, the top and bottom will come apart. A couple of screws later and you have prime case material.



I drilled two holes to mount the Pi and trimmed the rear opening just a little. It needs a better power connector.














One good thing about these cases, they stack very nicely.

Now to search the pile of junk for a hard drive. There is enough room to add one in this case.













Works Great!