Friday, December 19, 2014

AcuRite Weather Station, Barometric Pressure Part 6

Part 5 of this interminable series is here <link>

Way back in part 1 of this project I mentioned that I got into a little spat with a software developer about bringing up a linux version of his code <link>?  Well, I prowled around (a lot) in his forum and discovered that he really likes to talk.  That's a good thing, a very good thing, and I certainly can't say anything bad, I've got what? 200 or so posts on various technical items on this blog, and a trail a mile wide across the internet, so something about glass houses comes to mind.

At any rate, I ran across this post of his where he discusses the barometric pressure reading from the AcuRite console <link>.  The sensor is inexpensive, but it's a high sensitivity device and capable of measuring altitude to within 20cm if it is properly used.  What does altitude have to do with barometric pressure?  Everything.

I'm not going to go into the ins and outs of altitude vs temperature vs air pressure because there's entire sites devoted to this that will do a much better job of explaining it, except where it involves getting a reading worth using from the Acurite sensor in the console of my weather station.  It's the last piece of weather information I want to grab from the station; I want battery level from the weather head, but that's not weather info.  The rest of the stuff like rainfall this week, highest recorded temperature, those things can all be derived from the other data, and is a big piece of why I broke into the unit.

But, the barometric pressure still eludes me.  So, here's what I've learned so far and I'm quite open to suggestions.  The barometric sensor is: Measurement Specialities MS5607-02BA03 and here's its spec sheet PDF file <link> and the manufacturer's web page <link>.  It's a very nice device that uses a piezo crystal to sense pressure.  Since it's subject to temperature variance, it's read in the factory and calibration parameters are stored inside the chip.  The idea is that the developer resets the chip, reads the calibration parameters and then applies them to the reading from the chip and comes out with an extremely accurate measurement for altitude.  For a device in a fixed location, the altitude reading can be directly translated to atmospheric pressure.  That would give you an extremely accurate barometer.

Getting back to the forum post above, it's pretty clear that AcuRite doesn't do this.  They have some secret algorithm that is supposed to take the need for reading the calibration out by sampling readings for about a month to derive a value that will allow them to predict the weather more accurately for the console's location.  Pardon me if I'm a bit skeptical about that.  Weather forecasters have been trying to do that for centuries, and I hardly think AcuRite achieved the 'Holy Grail' of atmospheric science.  I think they couldn't figure out how to work the chip and gave up and called marketing in for a story to cover up the problem.

Who wants to buy a barometer that you have to wait a month before the readings start making sense?

We don't need accuracy in the absolute reading, we need accuracy in the changing of the reading, and a linear response that we can calibrate and forget for a while.  Remember the old physical barometers that our farmer grandparents had in the front room where you could check it on the way out the door to the barn?  Those devices worked well.  When you got them, you checked the local weather and set the reading to be the same by turning a screw on the back.  Then you set a movable needle (ours was red) to point to the same reading.  Then if the pressure went up (clear weather) you could tell because the needle moved up from the red one.  Simple, and did the job nicely.

I logged the data from the R2 message (where the barometer is supposed to show up) for quite a while, but still haven't figured it out.  In a fit of pique, I pulled the console apart to see if I could read the chip's markings:

Tearing open the AcuRite Console


Above is an over all view of the guts of the thing.  Circled on the left near the middle is the USB port and power input; above it is the board for the local temperature and humidity sensor; and way on the right is the radio receiver.  The antenna is a wire that runs up and to the left along the top.  Yes, they covered the logic chips to try and keep us from reverse engineering the hardware.


Here's the local temperature sensor (black) and local humidity sensor (white) next to an air vent. Simple setup and should work well.


The lower logic board with the reset and radio channel select switches.


The upper logic board.  The arrow points to the pressure sensor and I'm sorry it's hard to see, but you already know what it looks like from the data sheet.  This tiny little thing was hard to read and took my best magnifying glass and a good flashlight to see the markings.

All in all, it looks like a reasonable device, it makes me wonder why they took the path they chose for the barometric sensor; they seem to have enough horsepower in there to get a really good reading.

I went back to prowling through the website above, looking specifically for hints on how to derive the pressure reading from the data the device sends and came across this explanation:
In fact, the sensors used in these consoles aren't accurate; you can't expect to get an "accurate" barometric sensor for anything less than about $1,600.  That's the reason your sensor is reporting over 40 inches of mercury; we adjust for the variations between sensors using the adjustment you were asked to do.
But I don't buy that explanation.  I've seen a bunch of gauges that cost far less than $1600, with specs that are pretty impressive, and this device claims accuracy to mere centimeters.  Nope, it's simply that the data is not corrected using the burned in parameters that should be used.

So, given the various graphs provided by the manufacturer the device is relatively linear, just way off because the calibration hasn't been done, so how the heck do I find the pressure in the data?  If I can get the pressure, then rely on the temperature inside a house to be relatively stable, we should be able to derive an offset that gives us a pretty reasonable value.  Here's a small sample of the data sent in the R2 message.

02 00 00 80 00 00 00 00 00 04 00 10 00 00 00 09 60 01 01 01 01 90 07 47 E0
02 00 00 80 00 00 00 00 00 04 00 10 00 00 00 09 60 01 01 01 01 90 05 47 E0
02 00 00 80 00 00 00 00 00 04 00 10 00 00 00 09 60 01 01 01 01 90 05 47 DC
02 00 00 80 00 00 00 00 00 04 00 10 00 00 00 09 60 01 01 01 01 90 05 47 D9
02 00 00 80 00 00 00 00 00 04 00 10 00 00 00 09 60 01 01 01 01 90 03 47 D9
02 00 00 80 00 00 00 00 00 04 00 10 00 00 00 09 60 01 01 01 01 90 03 47 D6
02 00 00 80 00 00 00 00 00 04 00 10 00 00 00 09 60 01 01 01 01 90 05 47 D6
02 00 00 80 00 00 00 00 00 04 00 10 00 00 00 09 60 01 01 01 01 90 07 47 D6
02 00 00 80 00 00 00 00 00 04 00 10 00 00 00 09 60 01 01 01 01 90 09 47 D6
02 00 00 80 00 00 00 00 00 04 00 10 00 00 00 09 60 01 01 01 01 90 09 47 D1

The reading on the console for the last line was 77 F and 30.09 in/Hg.  Notice how only the last three bytes change?  Actually though the last four bytes change.  Heres the highest and lowest values sorted from the file I'm logging to:

02 00 00 80 00 00 00 00 00 04 00 10 00 00 00 09 60 01 01 01 01 8F DF 47 B0
02 00 00 80 00 00 00 00 00 04 00 10 00 00 00 09 60 01 01 01 01 90 3B 47 BB

Yes, the other values in the data do not change.  It was reported on the forum that the calibration values were being sent, but this isn't the case I my examination.  If these are calibration values, they don't resemble the items described by the manufacturer, nor do they make sense. So, we're left with the last four 8 bit values.  The data supplied by the sensor is in 24 bit values, so these last bytes could not be all of the temperature and pressure.  Since this is made in Europe, it's likely the data is in degrees C and mbar, and just looking at the four bytes as pressure, temperature, the last line of the first block could be, 0x9009 = 38873 which doesn't seem to be reasonable.  It might be somewhat reasonable for in/Hg though as in 38.8 in/Hg; a bit high, but sort of there.  I don't get much sense out of the other number 0x47d1 = 18385 either.

But notice that the second 16 bit number is roughly half the first number; it might be that the first number is a higher conversion from the A/D convertor than the second and both of them represent the same thing, just different settings on the A/D convertor.  The chip supports this, so it's possible.

And remember, I can't compare the numbers to the reading on the front of the display since AcuRite is using some secret algorithm to compute that because their way is more accurate, meaningful, whatever...

The low pressure for the last 24 hours was 952 mbar (28.11 in/Hg) and the high was 956 (28.23) and from the graph I looked at the average was 954 (28.17), so suppose the first 16 bit number was the pressure reading (uncompensated) and I just took a 24 hour average to see what came up, a change of 23 in the number would be a 1 millibar change in pressure.  If I just go with the average of 954 and consider the mean of my high and low reading 36877 and bump it up by one tenth for every 2.3  change in the number, to see what happens:

36831 to 36923 avg 36877  difference = 92
952 to 956 avg is 954  difference = 4
That would make each milibar increase equal to a change of 23 in the reading.

So, if I set 954 as being the same as 36877 and a reading of 8fe7 (right this second) would be 36839, subtract 36877 to get -38 divide by 23 to get -1.65 and the pressure would be 952.4 mbar.  Which is more reasonable than the display which says 1017 mbar, which would put me below seal level and a bunch higher than all the stations around me.  Also, remember that what I'm really interested in is the change over time as a forecast indicator.

All told, it looks to me like the barometric pressure is a total waste of time to continue trying to decode and make sense of beyond this.  It appears that AcuRite ignores the correction factors in favor of some secret technique, and we all know how I feel about 'secrets'.  I'll try this for a few days to see how it goes, but my big inclination is to get a pressure sensor and use some of the example code out there to read it and properly apply the compensation factors to get a really good reading.  I may not use the same chip, because there's a lot of breakout boards for the BMP180 and they're cheap.

Hmm, I could hook a BMP180 up to an Arduino, get both the temperature and barometric pressure from it, send it through an XBee to my Pi controller and replace the configuration I have outside in the Stephenson screen.  That would actually be a cool project.

If you notice something I overlooked, misunderstood, or messed up, let me know.  

Tuesday, December 16, 2014

Acurite Weather Station, Raspberry Pi, USB driver, I messed up, Part 5

Part 4 of this series is here <link>

Yep, I messed up, here's the story:  I was logging data so I could try and decode the barometric sensor and was getting nowhere, so I thought I'd take the console apart to see what kind of pressure sensor it had in it.  So, about an hour later I plugged it back in and it didn't work.

No, I didn't suspect I broke it, everything worked except the actual read.  The reads timed out and then started responding that I was sending the wrong command.  No code had changed, what could be wrong?  After the usual routine of changing cables, checking power, plugging it in and out a bunch of times, it still didn't work.  I moved the console to my other Pi and started taking apart the code.

About a day later, I had tried everything I could think of then it dawned on me that something might be wrong with the initialization.  I found the problem, and it was with the way I set it up.  I removed the attachment of the hidraw driver from the port, and it was providing the initialization to the kernal that allowed the weather station to work.  That was a bad thing, since without this, the kernal couldn't talk to the station.

When I took the command out of the command.txt file and prevented the hidraw driver from connecting, the communication path was fine.  But (there's always one of those), my code couldn't connect a second time.  The first time I plugged it in, everything was fine, but if the device was reset, it started failing again.  This turned out to be caused by my code disconnecting the hidraw driver and not putting it back so it could initialize the device again.

Fortunately, there's fix for that; usblib has a call that will detach the kernal driver automatically and restore it when we're done.  I added that and the station can be plugged in and work, then work again the next time.  While I was in there, I cleaned up some comments, removed the superfluous code that I left in, and generally cleaned up a bit.

It's working fine now and back to everyday use while I look further into the barometric pressure, but that's yet another post on this device.  Meanwhile, here's the updated code for the usb driver portion, and the changes will be in Github in an hour or so.

/*
    Documentation at desert-home.com
    
    This is the actual weather module I run for controlling the house, as such, this 
    may not be what you want.  You may want other-stuff/weatherstation.c

    Experimentation with a USB interface to the Acu-Rite 5 in 1 Weatherstation
    specifically for the Raspberry Pi.  The code may work on other systems, but I 
    don't have one of those.  Contrary to other folk's thinking, I don't care if 
    this ever runs on a Windows PC or an Apple anything.
    
    I specifically used a model 2032 display with the 5 in 1 sensor that I picked
    up at one of those warehouse stores.  The display has a usb plug on it and 
    I thought it might be possible to read the usb port and massage the data myself.
    
    This code represents the result of that effort.
    
    I gathered ideas from all over the web.  I use the latest (for this second)
    libusb and about a thousand examples of various things that other people have 
    done and posted about.  Frankly, I simply can't remember all of them, so please,
    don't be offended if you see your ideas somewhere in here and it isn't attributed.
    
    I simply lost track of where I found what.
    
    This module relies on libusb version 1.0.19 which, at this time, can only be
    compiled from source on the raspberry pi.
    
    Because there's likely to be a version of libusb and the associated header file
    on a Pi, use the command line below to build it since the build of libusb-1.0.19
    places things in /usr/local
    
    cc -o weatherstation  weatherstation.c -L/usr/local/lib -lusb-1.0    
    use ldd weatherstation to check which libraries are linked in.
    If you still have trouble with compilation, remember that cc has a -v
    parameter that can help you unwind what is happening.
*/

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <sys/time.h>
#include <libusb-1.0/libusb.h>

// The vendor id and product number for the AcuRite 5 in 1 weather head.
#define VENDOR 0x24c0
#define PRODUCT 0x0003

// I store things about the weather device USB connection here.
struct {
    libusb_device *device;
    libusb_device_handle *handle;
} weatherStation;
// These are the sensors the the 5 in 1 weather head provides
struct {
    float   windSpeed;
    time_t  wsTime;
    int     windDirection;
    time_t  wdTime;
    float   temperature;
    time_t  tTime;
    int     humidity;
    time_t  hTime;
    int     rainCounter;
    time_t  rcTime;
} weatherData;

// This is just a function prototype for the compiler
void closeUpAndLeave();

// I want to catch control-C and close down gracefully
void sig_handler(int signo)
{
  if (signo == SIGINT)
    fprintf(stderr,"Shutting down ...\n");
    closeUpAndLeave();
}

/*
This tiny thing simply takes the data and prints it so we can see it
*/
// Array to translate the integer direction provided to text
char *Direction[] = {
    "NNW",
    "NW",
    "WNW",
    "W",
    "WSW",
    "SW",
    "SSW",
    "S",
    "SSE",
    "SE",
    "ESE",
    "E",
    "ENE",
    "NE",
    "NNE",
    "N"  };
// this is a bitmapped byte to tell if the various styles of reports have
// come in.  Bit 0 is R1 first type, bit 2 is R1 type 2 and bit 3 is R2
// even though I don't use R2 yet
uint8_t reportsSeen = 0;

void showit(){

    // make sure enough reports have come in before reporting
    if( reportsSeen >= 3){  // Change this when report 3 is decoded
        fprintf(stdout, "{\"windSpeed\":{\"WS\":\"%.1f\",\"t\":\"%d\"},"
                        "\"windDirection\":{\"WD\":\"%s\",\"t\":\"%d\"},"
                        "\"temperature\":{\"T\":\"%.1f\",\"t\":\"%d\"},"
                        "\"humidity\":{\"H\":\"%d\",\"t\":\"%d\"},"
                        "\"rainCounter\":{\"RC\":\"%d\",\"t\":\"%d\"}}\n",
                weatherData.windSpeed, weatherData.wsTime,
                Direction[weatherData.windDirection],weatherData.wdTime,
                weatherData.temperature, weatherData.tTime,
                weatherData.humidity, weatherData.hTime,
                weatherData.rainCounter, weatherData.rcTime);
        fflush(stdout);
    }
}
/* 
This code translates the data from the 5 in 1 sensors to something 
that can be used by a human.
*/
float getWindSpeed(char *data){
    int leftSide = (data[3] & 0x1f) << 3;
    int rightSide = data[4] & 0x70 >> 4;
    // Yes, I use mph, never got used to kilometers.
    return((float)(leftSide | rightSide) * 0.62);
}
int getWindDirection(char *data){
    return(data[4] & 0x0f);
}
float getTemp(char *data){
    // This item spans bytes, have to reconstruct it
    int leftSide = (data[4] & 0x0f) << 7;
    int rightSide = data[5] & 0x7f;
    float combined = leftSide | rightSide;
    return((combined - 400) / 10.0);
}
int getHumidity(char *data){
    int howWet = data[6] &0x7f;
    return(howWet);
}
int getRainCount(char *data){
    int count = data[6] &0x7f;
    return(count);
}
// Now that I have the data from the station, do something useful with it.

void decode(char *data, int length, int noisy){
    //int i;
    //for(i=0; i<length; i++){
    //    fprintf(stderr,"%0.2X ",data[i]);
    //}
    //fprintf(stderr,"\n"); */
    reportsSeen |= 0x04;  // just pretend I've seen report 2 already
    time_t seconds = time (NULL);
    //There are two varieties of data, both of them have wind speed
    // first variety of the data
    if ((data[2] & 0x0f) == 1){ // this has wind speed, direction and rainfall
        if(noisy)
            fprintf(stderr,"Wind Speed: %.1f ",getWindSpeed(data));
        weatherData.windSpeed = getWindSpeed(data);
        weatherData.wsTime = seconds;
        if(noisy)
            fprintf(stderr,"Wind Direction: %s ",Direction[getWindDirection(data)]);
        weatherData.wdTime = seconds;
        weatherData.windDirection = getWindDirection(data);
        if(noisy){
            fprintf(stderr,"Rain Counter: %d ",getRainCount(data));
            fprintf(stderr,"\n");
        }
        weatherData.rainCounter = getRainCount(data);
        weatherData.rcTime = seconds;
        reportsSeen |= 0x01; //I've seen report 1 now
    }
    // this is the other variety
    if ((data[2] & 0x0f) == 8){ // this has wind speed, temp and relative humidity
        if(noisy)
            fprintf(stderr,"Wind Speed: %.1f ",getWindSpeed(data));
        weatherData.windSpeed = getWindSpeed(data);
        weatherData.wsTime = seconds;
        if(noisy)
            fprintf(stderr,"Temperature: %.1f ",getTemp(data));
        weatherData.temperature = getTemp(data);
        weatherData.tTime = seconds;
        if(noisy){
            fprintf(stderr,"Humidity: %d ", getHumidity(data));
            fprintf(stderr,"\n");
        }
        weatherData.humidity = getHumidity(data);
        weatherData.hTime = seconds;
        reportsSeen |= 0x02;  // I've seen report 2 now

    }
}
/*
This code is related to dealing with the USB device
*/
// This searches the USB bus tree to find the device
int findDevice(libusb_device **devs)
{
    libusb_device *dev;
    int err = 0, i = 0, j = 0;
    uint8_t path[8]; 
    
    while ((dev = devs[i++]) != NULL) {
        struct libusb_device_descriptor desc;
        int r = libusb_get_device_descriptor(dev, &desc);
        if (r < 0) {
            fprintf(stderr,"Couldn't get device descriptor, %s\n", libusb_strerror(err));
            return(1);
        }

        fprintf(stderr,"%04x:%04x (bus %d, device %d)",
            desc.idVendor, desc.idProduct,
            libusb_get_bus_number(dev), libusb_get_device_address(dev));
        fprintf(stderr,"\n");
        if (desc.idVendor == VENDOR && desc.idProduct == PRODUCT){
            fprintf(stderr,"Found the weather station\n");
            weatherStation.device = dev;
            return (1);
        }
    }
    return(0);
}

// to handle testing and try to be clean about closing the USB device,
// I'll catch the signal and close off.
void closeUpAndLeave(){
    //OK, done with it, close off and let it go.
    fprintf(stderr,"Done with device, release and close it\n");
    int err = libusb_release_interface(weatherStation.handle, 0); //release the claimed interface
    if(err) {
        fprintf(stderr,"Couldn't release interface, %s\n", libusb_strerror(err));
    }
    fprintf(stderr, " Released interface and restored kernal driver\n");
    libusb_close(weatherStation.handle);
    fprintf(stderr, " Closed Weatherstation device\n");
    libusb_exit(NULL);
    fprintf(stderr, " Closed USB interface\n");
    exit(0);
}

// This is where I read the USB device to get the latest data.
unsigned char data[50]; // where we want the data to go

int getit(int whichOne, int noisy){
    int actual; // how many bytes were actually read
    
    // The second parameter is bmRequestType and is a bitfield
    // See http://www.beyondlogic.org/usbnutshell/usb6.shtml
    // for the definitions of the various bits.  With libusb, the 
    // #defines for these are at:
    // http://libusb.sourceforge.net/api-1.0/group__misc.html#gga0b0933ae70744726cde11254c39fac91a20eca62c34d2d25be7e1776510184209

    actual = libusb_control_transfer(weatherStation.handle, 
        LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_IN,
        //These bytes were stolen with a USB sniffer
        0x01,0x0100+whichOne,0,
        data, sizeof(data), 10000);
    if (actual < 0){
        fprintf(stderr,"Read didn't work for report %d, %s\n", whichOne, libusb_strerror(actual));
    }
    else {
        // If you want both of the reports that the station provides,
        // just allow for it.  Right this second, I've found every thing
        // I need in report 1.  When I look further at report 2, this will
        // change
        //fprintf(stderr,"R%d:%d:", whichOne, actual);
        //int i;
        //for(i=0; i<actual; i++){
        //    fprintf(stderr,"%0.2X ",data[i]);
        //}
        //fprintf(stderr,"\n");
        if (whichOne == 1)
            // The actual data starts after the first byte
            // The first byte is the report number returned by 
            // the usb read.
            decode(&data[1], actual-1, noisy);
    }
}
// I do several things here that aren't strictly necessary.  As I learned about
// libusb, I tried things and also used various techniques to learn about the 
// weatherstation's implementation.  I left a lot of it in here in case I needed to
// use it later.  Someone may find it useful to hack into some other device.
int main(int argc, char **argv)
{
    char *usage = {"usage: %s -u -n\n"};
    int libusbDebug = 0; //This will turn on the DEBUG for libusb
    int noisy = 0;       //This will print the packets as they come in
    libusb_device **devs;
    int r, err, c;
    ssize_t cnt;
    
    while ((c = getopt (argc, argv, "unh")) != -1)
        switch (c){
            case 'u':
                libusbDebug = 1;
                break;
            case 'n':
                noisy = 1;
                break;
            case 'h':
                fprintf(stderr, usage, argv[0]);
            case '?':
                exit(1);
            default:
                exit(1);
       }
    fprintf(stderr,"%s Starting ... ",argv[0]);
    fprintf(stderr,"libusbDebug = %d, noisy = %d\n", libusbDebug, noisy);
    // The Pi linker can give you fits.  If you get a linker error on
    // the next line, set LD_LIBRARY_PATH to /usr/local/lib 
    fprintf(stderr,"This is not an error!! Checking linker, %s\n", libusb_strerror(0));

    if (signal(SIGINT, sig_handler) == SIG_ERR)
        fprintf(stderr,"Couldn't set up signal handler\n"); 
    err = libusb_init(NULL);
    if (err < 0){
        fprintf(stderr,"Couldn't init usblib, %s\n", libusb_strerror(err));
        exit(1);
    }
    // This is where you can get debug output from libusb.
    // just set it to LIBUSB_LOG_LEVEL_DEBUG
    if (libusbDebug)
        libusb_set_debug(NULL, LIBUSB_LOG_LEVEL_DEBUG);
    else
        libusb_set_debug(NULL, LIBUSB_LOG_LEVEL_INFO);

    
    cnt = libusb_get_device_list(NULL, &devs);
    if (cnt < 0){
        fprintf(stderr,"Couldn't get device list, %s\n", libusb_strerror(err));
        exit(1);
    }
    // go get the device; the device handle is saved in weatherStation struct.
    if (!findDevice(devs)){
        fprintf(stderr,"Couldn't find the device\n");
        exit(1);
    }
    // Now I've found the weather station and can start to try stuff
    // So, I'll get the device descriptor
    struct libusb_device_descriptor deviceDesc;
    err = libusb_get_device_descriptor(weatherStation.device, &deviceDesc);
    if (err){
        fprintf(stderr,"Couldn't get device descriptor, %s\n", libusb_strerror(err));
        exit(1);
    }
    fprintf(stderr,"got the device descriptor back\n");
    
    // Open the device and save the handle in the weatherStation struct
    err = libusb_open(weatherStation.device, &weatherStation.handle);
    if (err){
        fprintf(stderr,"Open failed, %s\n", libusb_strerror(err));
        closeUpAndLeave();    }
    fprintf(stderr,"I was able to open it\n");
    // Now that it's opened, I can free the list of all devices
    libusb_free_device_list(devs, 1); // Documentation says to get rid of the list
                                      // Once I have the device I need
    fprintf(stderr,"Released the device list\n");
    err = libusb_set_auto_detach_kernel_driver(weatherStation.handle, 1);
    if(err){
        fprintf(stderr,"Some problem with detach %s\n", libusb_strerror(err));
        closeUpAndLeave();
    }
    int activeConfig;
    err =libusb_get_configuration(weatherStation.handle, &activeConfig);
    if (err){
        fprintf(stderr,"Can't get current active configuration, %s\n", libusb_strerror(err));;
        closeUpAndLeave();
    }
    fprintf(stderr,"Currently active configuration is %d\n", activeConfig);
    if(activeConfig != 1){
        err = libusb_set_configuration  (weatherStation.handle, 1);
        if (err){
            fprintf(stderr,"Cannot set configuration, %s\n", libusb_strerror(err));;
        closeUpAndLeave();
        }
        fprintf(stderr,"Just did the set configuration\n");
    }
    
    err = libusb_claim_interface(weatherStation.handle, 0); //claim interface 0 (the first) of device (mine had just 1)
    if(err) {
        fprintf(stderr,"Cannot claim interface, %s\n", libusb_strerror(err));
        closeUpAndLeave();
    }
    fprintf(stderr,"Claimed Interface\n");
    // I don't want to just hang up and read the reports as fast as I can, so
    // I'll space them out a bit.  It's weather, and it doesn't change very fast.
    sleep(1);
    fprintf(stderr,"Setting the device 'idle' before starting reads\n");
    err = libusb_control_transfer(weatherStation.handle, 
        0x21, 0x0a, 0, 0, 0, 0, 1000);
    sleep(1);
    int tickcounter= 0;
    fprintf(stderr,"Starting reads\n");
    while(1){
        sleep(1);
        if(tickcounter++ % 10 == 0){
            getit(1, noisy);
        }
        if(tickcounter % 30 == 0){
            getit(2, noisy);
        }
        if (tickcounter % 15 == 0){
            showit();
        }
    }
}

There's more details on what not to do in the post where I described this in detail <link>, yep, I fessed up right in the post where the instructions are.

For the folk that have already grabbed this, sorry, I'll try a bit harder next time.

Part 6 of this series is here <link>

Saturday, December 13, 2014

AcuRite Weather Station, Raspberry Pi and a USB interface On the Web Part 4

Part 3 of this is here <link>

After I finished up changing how my various processes communicate in the last post, I got back to the weather station.  Frankly, it's no fun reading lines of text, it's much better to put things in guages or charts.  I took the easy way out, and just put the entire JSON string I created in the data data base as a string, not separate records.

No, there wasn't any technical reason for this, I just didn't want to think about how to organize the schema to handle it.  It does mean that I haven't done anything with the rain fall counter though.  This part of the station is a simple accumulator that counts rainfall in 0.01 inch increments.  We have to save the measurement periodically and calculate from the latest reading to have it make sense.  I'll get to that some time this winter, but I have the wind direction and speed, temperature, and humidity working just fine.

It took some experimentation to get the JSON string out of the database and converted into values in PHP though.  Take a look at the code involved:

$ws = timedQuerySingle(
 'select "json" from "weather";');
$db->close();
# The weather string is a pain, this is converting it, and
# since I can reuse variables and I'm tired of thinking up names
# I use the same name over and over again just to confuse
# anyone reading this.
# First, there an extra set of quotes around the string
$ws=substr($ws,1,strlen($ws)-2);
# Now I have to get rid of the \" that I had to use to put it in
# the database
$ws=str_replace("\\","",$ws);
#Now, convert the json into variables
$ws = json_decode($ws,true);

The entire string is surrounded by quotes and every single quote inside is 'escaped' with a backslash.  So, I cut out the middle between the quotes and then remove the backslashes.  This won't work if there's a backslash in the string, but I built the string and know what's in it.  Once I'm done with this, the various items in the string are available as a named array index. Here's how to refer to the four values temperature, wind speed, wind direction and humidity:

$ws["windSpeed"]["WS"]
$ws["windDirection"]["WD"]
$ws["humidity"]["H"]
$ws["temperature"]["T"]);

If you remember, I stored the time I recorded the reading with each item, so the times would be something like,

$ws["windDirection"]["t"]

for each measurement.  I don't know if I need it, but it's better to have it now while I'm figuring out what I want to keep.

To display it I added even more SteelSeries gauges to my display for the items.  I now looks like this:


Yes, it's a little busy, but I can glace at it and tell what's going on in a second; besides, it's mine, not some other piece of software that I have to mess with forever to get what I want.  I suspect it will get even more elaborate when I add in something for rainfall.  Heck, I may use one of the web authoring tools and really fancy up the display to impress people with, but this is adequate for taking a look at what it's like outside, and it's only a few seconds behind real time.  I'm actually pretty proud of it.

And yes, it's raining today; the humidity is usually around 12 or so.  The difference between the two temperatures is related to location.  The weather station is up on the roof, and the other temperature monitor is on a fence post at about 5 feet.  On sunny days I expect the temperature difference to be even larger.  As before, the code is available on github to grab and change to suit your needs.

Have fun.

Part 5 to this incredibly long series is here <link>.

Tuesday, December 9, 2014

Using CherryPy for interprocess Communication Part 3

Part 1 is here <link>, and part 2 is here <link>.

I've been devoting my project time to the weather station, and completely forgot about finishing my testing of using HTTP to communicate between processes.  It's time to finish the experiment and actually implement it for the various processes I have running on the Pi.  As I mentioned before, I want to do this so I can move any of the processes to a different machine when I finally outgrow the single Raspberry PI.  No, I'm not changing machines, I'm going to add others to the collection.

I started with the code that controls the Wemo light switches I have and let it run for a while to make sure there wasn't something weird going to pop up; it didn't.  So now is the time to make corresponding changes to the code that sends signals to control the lights.  Currently there are two places I do this: a process that runs and handles scheduled events around the house and the web interface I use to manually control and look at things.  I'll start with the event controller since it's the easiest piece of code.

Basically, it's just a matter of sending an HTML request to the address and port number I assigned to the Wemo code.  I use different port numbers so I can have multiple addresses on a single machine.  This way, the  regular web server watches on port 80 like all the web sites out there, and the control processes watch on whatever port I decided to put them on.  To do this and maintain versatility, I put the address in my .houserc file and read it when the process first comes up.  Here's the entry I have:

"wemocontrol":{
"ipAddress":"192.168.0.205",
"port": 51001},

I just read the file using the code I already posted here <link> and set the Cherry Pi server to listen to it.  Now I need to have some code to send to it.  Here's the code fragment I came up with:

#! /usr/bin/python
import datetime
import time
import urllib2
import BaseHTTPServer
import os, sys
lib_path = os.path.abspath('../house')
sys.path.append(lib_path)
from houseutils import lprint, getHouseValues, timer, checkTimer

def openSite(Url):
 #print Url
 try:
  webHandle = urllib2.urlopen(Url, timeout=5) #if it doesn't answer in 5 seconds, it won't
 except urllib2.HTTPError, e:
  errorDesc = BaseHTTPServer.BaseHTTPRequestHandler.responses[e.code][0]
  print "Error: cannot retrieve URL: " + str(e.code) + ": " + errorDesc
  raise
 except urllib2.URLError, e:
  print "Error: cannot retrieve URL: " + e.reason[1]
  raise
 except urllib2.HTTPError as e:
  print e.code
  print e.read()
  raise
 except:  #I kept getting strange errors when I was first testing it
  e = sys.exc_info()[0]
  print ("Odd Error: %s" % e )
  raise
 return webHandle

def talktoWemo(ip, command):
 website = openSite("HTTP://" + ip + '/' + command)
 # now read the status that came back from it
 websiteHtml = website.read()
 # After getting the status from the little web server,
 # strip off the trailing cr,lf
 # and separate the values into a list that can
 # be used to tell what is going on
 return websiteHtml
    
# Get the ip address and port number you want to use
# from the houserc file
ipAddress=getHouseValues()["wemocontrol"]["ipAddress"]
port = getHouseValues()["wemocontrol"]["port"]
wemoController = ipAddress + ":" + str(port)
print(talktoWemo(wemoController, "status"))

print(talktoWemo (wemoController, "pCommand?command=patioToggle"))

Well, it's actually more than a fragment, This code will actually control my patio light.  Each time I run it, it will toggle the switch.  It turned out to be pretty simple when done in python, I just used the normal facilities for reading a web site.  In there are also examples of how I read the address and port number from the .houserc file, and how I handle possible errors in the HTTP request.  I came up with that huge set of error conditions as I was learning how to send and receive HTTP; there's a lot that can go wrong and I hated having to look up the errors each time I messed something up.

Now I need to understand how to do the same thing from php so I can command it with a request over the internet.  I guess while I'm in there I'll add some code to read the .houserc file; here's how I send the HTML request to the Wemo control software:

function ipControl($whichOne,$command){
    echo "<br />";
    echo "URL will be: $whichOne/$command";
    $response = file_get_contents("http://$whichOne/$command");
    return(true);

};

This is how I read the .houserc file to get the address I assigned it:

$config = file_get_contents("/home/pi/.houserc");

Then in the case statement for controlling the lights I decode the JSON request and grab the address and port number I put in there.

case "lights":
    $wemoIp = json_decode($config,true)["wemocontrol"]["ipAddress"];
    $wemoPort = json_decode($config,true)["wemocontrol"]["port"];
    $c = $commandParts[1];
    ipControl("$wemoIp:$wemoPort", "pCommand?command=$c");
    break;

This set of changes is merged into my php script that handles web commands, 'command.php'.  Yes, I managed to get it into github so you can grab the entire thing from there and see how it fits together.
The file is in the 'wwwsrc' directory and here's a link to the page <link>.

The very last thing is to include the fragment above into the scheduled event process.  This separate process handles events like: turn the outside lights on at 7PM; turn them off at 10PM, shut the pool motor off in case I forget, etc.  I haven't talked about this process much previously because it had several things in it that I didn't want to put on the web.  When I moved my various keys and such into the .houserc file, that problem disappeared, so this file has the changes above added to it and is called 'events.py' and is in the directory 'house' in the github page above.

So, one set of controls is done.  I'll have to visit each of the others over time and make similar changes to them, but I won't be limited to one machine anymore.  To move a process to another machine, just change the address in the .houserc file; HTTP interactions don't care that the process is on another machine.

Wednesday, December 3, 2014

AcuRite Weather Station, Raspberry Pi and a USB interface; Part 3

Part 2 of this is here <link>, and part 1 is here <link>.

Well, we've talked about the weather station, how to create a usb device for it and how to get a device driver that can talk to it.  Now we have to get the data off the usb and do something with it.

Edit:  I forgot a couple of items and one of the comments brought it to my attention.  Run the weather station in USB mode 4.  Two reasons, modes 3 and 4 are the modes that send output to the USB port, and there's bug in the station that can mess you up.  Other folk out there have reported to AcuRite that the device fills up and doesn't empty.  When you're running and saving data on the console, some bug causes the device to stop saving in a couple of weeks and you get an error message on the scrolling display that won't go away.  The message is something like, "Datalogger full.'  Naturally, I found this out AFTER I bought it.

The usb device has two reports it provides, they're called Report 1 and Report 2 (duh).  R1 is a few bytes long and R2 is longer.  R1 has all the data from the weather head in it while R2 appears to have data gathered by the console.  For my purposes, I don't need anything from the console except the barometric pressure.  The sensor for that is inside the console piece and I haven't figured out how to get that part yet.

Yes, let me admit right up front I haven't figured out the barometric pressure reading.  The way you break into these things is to read the data, change something, look at the change, repeat until you have it figured out.  Barometric pressure doesn't change fast or far enough for me to have that ... yet.  I'll get it at some point.

However, wind speed, direction, a rain counter, temperature and humidity are all available and we can get that stuff from the device that's up on the roof.  It's really cool to be able to see that stuff on the Pi, so let's deal with R1 and save R2 for sometime later.  Here's the two reports that we can get out of the device:

R1 - 01 C0 5C 71 00 05 00 0C 03 FF
R1 - 01 C0 5C 78 00 08 1F 53 03 FF
R2 - 02 00 00 80 00 00 00 00 00 04 00 10 00 00 00 09 60 01 01 01 01 90 01 47 68

R1 is 10 bytes long and R2 is 25 bytes long.  In each message the first byte is simply the report number and the last byte is the end marker, so the actual data for the two R1's are:

C0 5C 71 00 05 00 0C 03
C0 5C 78 00 08 1F 53 03

It's complicated by the fact that there are two types of R1 messages.  Which type each one is, is determined by the low order half of byte 2 -- remember, we start counting from 0.  In the first case, the lower 4 bits of byte 2 are 0001, this message contains wind speed, wind direction and rain counter.  For the second example, the lower 4 bits of byte 2 are 1000 and this message contains wind speed, temp and relative humidity.  To test for it in code:

if ((data[2] & 0x0f) == 8){ // this has wind speed, temp and relative humidity

and

if ((data[2] & 0x0f) == 1){ // this has wind speed, direction and rainfall

We've just obtained the readings of all five of the sensors in the 5 in 1 sensor head for the weatherstation.  Next we have to pick the bits out of each of them corresponding to the sensors reported.  So, I'll decode an item; the wind direction is the low order half of byte 4 (counting from 0) in message type 1, so the value would be 5.  Look above in the data, byte 2 is 71 so the low order half is 1 which means it holds wind speed, direction and rainfall; see how it works?

The 5 is one of the sixteen cardinal directions starting with NNW being 0 counting counter-clockwise around the compass and ending with N being 15.  That makes this SW, and 6 would be SSW and so on.  To decode this into something readable, just set up an array and index into it using the value you get from byte 4.

char *Direction[] = {"NNW", "NW", "WNW", "W", "WSW", "SW", "SSW", "S",
                                   "SSE", "SE",  "ESE", "E", "ENE", "NE", "NNE", "N" };

And then:

char *direction = Direction[data[4] & 0x0f];

Would give you the string "SW".  Not too hard, so let's look at something a bit harder to decode, the temperature.  Temperature is held in bytes 4 and 5 of message type 8.  Looking above at the data, the one with byte 2 = 78 (78 & 0x0f = 8) bytes 4 and five have the values 08 1F.  With these we have to combine the lower order 4 bits of byte 4 with the 7 lowest bits of byte 5.  The code for this looks like this:

    int highpart = (data[4] & 0x0f) << 7;
    int lowpart = data[5] & 0x7f;
    float combined = highpart | lowpart;

Notice how we can get a floating point number out of this?  We have to use the conversion capabilities of the compiler to make it easy, but it works nicely.  So in this case we get 08 & 0f which give us 0x08 then we shift it left 7 bits to result in 0x400.  Then we take the 0x1F & 0x7F to get 0x1F. Combining them 0x400 | 0x1f gets us 0x41F which is decimal 1055.  That seems a bit high, even for Arizona, so subtract 400 and divide by 10 to give a float value of 65.5 degrees Fahrenheit.  Fortunately, the compiler will do this by:

float temperature = (combined - 400) / 10.0;

Are you starting to see how this works?  I decoded the rest of them and the code that comes later will give you all the details for the other sensor data.  Never mind that every single thing is carefully designed to be confusing.  The compass cardinals count counter-clockwise; all the high order bits of the sensor data are for parity, not values; The readings have an offset of 400; etc.  This stuff is just details that, once figured out and coded, aren't even relevant any more.  You only have to be confused once, and I did that for you already.

We have the ability to decode the sensors, but how did I get the data?  Remember in the last episode I opened and closed the USB device but didn't read it?  It's time for me to show you how to read it.  With a USB device, you don't read it, you 'transfer' it; this is what it looks like:

actual = libusb_control_transfer(weatherStation.handle,
                    LIBUSB_REQUEST_TYPE_CLASS |
                    LIBUSB_RECIPIENT_INTERFACE |
                    LIBUSB_ENDPOINT_IN,
                    //These bytes were stolen with a USB sniffer
                    0x01,0x0100+whichOne,0,
                    data, 50, 10000);

We had already found the device, opened it and reserved it use for ourselves, so this transfer is the last piece.  The bits above are documented with libusb and the numeric values were totally stolen by sniffing the USB interaction and doing some copy and paste.  The very last three items in the call are the pointer to where the data is going to wind up, the maximum length acceptable and a maximum wait time of 10 seconds (in milliseconds).  Yep, that's all there is to it getting the data.  I didn't want to hang up in a hard loop banging the USB port as fast as I could because it wouldn't leave the weather station any time to actually process the incoming data from the sensor head.  I set up a really simple timer to read the USB port periodically to get the report R1 and process it into usable data.

I'm not real proud of the way I timed it, but it works.  I put the code in to read both of the reports, R1 and R2 because I haven't given up on the barometric pressure; I totally will decode that someday.  Once I got the code to read the data, convert it and print it, I decided to fancy it up a bit.  I like working on this kind of stuff in python, so I made the 'c' code send its output to stderr and stdout.  Stderr gets the informational, and error messages while stdout gets a ... you guessed it ... JSON ... message that can be read into python and further processed.  The idea is that you pipe one process into another using the Linux model.  First the code that I'm currently running to get a feel for what I want to do in the future:

/*
    Experimentation with a USB interface to the Acu-Rite 5 in 1 Weatherstation
    specifically for the Raspberry Pi.  The code may work on other systems, but I 
    don't have one of those.  Contrary to other folk's thinking, I don't care if 
    this ever runs on a Windows PC or an Apple anything.
    
    I specifically used a model 2032 display with the sensor that I picked
    up at one of those warehouse stores.  The display has a usb plug on it and 
    I thought it might be possible to read the usb port and massage the data myself.
    
    This code represents the result of that effort.
    
    I gathered ideas from all over the web.  I use the latest (for this second)
    libusb and about a thousand examples of various things that other people have 
    done and posted about.  Frankly, I simply can't remember all of them, so please,
    don't be offended if you see your ideas somewhere in here and it isn't attributed.
    
    I simply lost track of where I found what.
    
    This module relies on libusb version 1.0.19 which, at this time, can only be
    compiled from source on the raspberry pi.
    
    Because there likely to be a version of libusb and the associated header file
    on a Pi, use the command line below to build it since the build of libusb-1.0.19
    places things in /usr/local
    
    cc -o weatherstation  weatherstation.c -L/usr/local/lib -lusb-1.0    
    use ldd weatherstation to check which libraries are linked in.
    If you still have trouble with compilation, remember that cc has a -v
    parameter that can help you unwind what is happening.
*/

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <sys/time.h>
#include <libusb-1.0/libusb.h>

// The vendor id and product number for the AcuRite 5 in 1 weather head.
#define VENDOR 0x24c0
#define PRODUCT 0x0003

// I store things about the weather device USB connection here.
struct {
    libusb_device *device;
    libusb_device_handle *handle;
    int verbose;
} weatherStation;
// These are the sensors the the 5 in 1 weather head provides
struct {
    float   windSpeed;
    time_t  wsTime;
    int     windDirection;
    time_t  wdTime;
    float   temperature;
    time_t  tTime;
    int     humidity;
    time_t  hTime;
    int     rainCounter;
    time_t  rcTime;
} weatherData;

// This is just a function prototype for the compiler
void closeUpAndLeave();

// I want to catch control-C and close down gracefully
void sig_handler(int signo)
{
  if (signo == SIGINT)
    fprintf(stderr,"Shutting down ...\n");
    closeUpAndLeave();
}

/*
This tiny thing simply takes the data and prints it so we can see it
*/
// Array to translate the integer direction provided to text
char *Direction[] = {
    "NNW",
    "NW",
    "WNW",
    "W",
    "WSW",
    "SW",
    "SSW",
    "S",
    "SSE",
    "SE",
    "ESE",
    "E",
    "ENE",
    "NE",
    "NNE",
    "N"  };

void showit(){

    fprintf(stdout, "{\"windSpeed\":{\"WS\":\"%.1f\",\"t\":\"%d\"},"
                    "\"windDirection\":{\"WD\":\"%s\",\"t\":\"%d\"},"
                    "\"temperature\":{\"T\":\"%.1f\",\"t\":\"%d\"},"
                    "\"humidity\":{\"H\":\"%d\",\"t\":\"%d\"},"
                    "\"rainCounter\":{\"RC\":\"%d\",\"t\":\"%d\"}}\n",
            weatherData.windSpeed, weatherData.wsTime,
            Direction[weatherData.windDirection],weatherData.wdTime,
            weatherData.temperature, weatherData.tTime,
            weatherData.humidity, weatherData.hTime,
            weatherData.rainCounter, weatherData.rcTime);
    fflush(stdout);
}
/* 
This code translates the data from the 5 in 1 sensors to something 
that can be used by a human.
*/
float getWindSpeed(char *data){
    int leftSide = (data[3] & 0x1f) << 3;
    int rightSide = data[4] & 0x70 >> 4;
    // Yes, I use mph, never got used to kilometers.
    return((float)(leftSide | rightSide) * 0.62);
}
int getWindDirection(char *data){
    return(data[4] & 0x0f);
}
float getTemp(char *data){
    // This item spans bytes, have to reconstruct it
    int leftSide = (data[4] & 0x0f) << 7;
    int rightSide = data[5] & 0x7f;
    float combined = leftSide | rightSide;
    return((combined - 400) / 10.0);
}
int getHumidity(char *data){
    int howWet = data[6] &0x7f;
    return(howWet);
}
int getRainCount(char *data){
    int count = data[6] &0x7f;
    return(count);
}
// Now that I have the data from the station, do something useful with it.
void decode(char *data, int length, int noisy){
    //int i;
    //for(i=0; i<length; i++){
    //    fprintf(stderr,"%0.2X ",data[i]);
    //}
    //fprintf(stderr,"\n"); */
    time_t seconds = time (NULL);
    //There are two varieties of data, both of them have wind speed
    // first variety of the data
    if ((data[2] & 0x0f) == 1){ // this has wind speed, direction and rainfall
        if(noisy)
            fprintf(stderr,"Wind Speed: %.1f ",getWindSpeed(data));
        weatherData.windSpeed = getWindSpeed(data);
        weatherData.wsTime = seconds;
        if(noisy)
            fprintf(stderr,"Wind Direction: %s ",Direction[getWindDirection(data)]);
        weatherData.wdTime = seconds;
        weatherData.windDirection = getWindDirection(data);
        if(noisy){
            fprintf(stderr,"Rain Counter: %d ",getRainCount(data));
            fprintf(stderr,"\n");
        }
        weatherData.rainCounter = getRainCount(data);
        weatherData.rcTime = seconds;
    }
    // this is the other variety
    if ((data[2] & 0x0f) == 8){ // this has wind speed, temp and relative humidity
        if(noisy)
            fprintf(stderr,"Wind Speed: %.1f ",getWindSpeed(data));
        weatherData.windSpeed = getWindSpeed(data);
        weatherData.wsTime = seconds;
        if(noisy)
            fprintf(stderr,"Temperature: %.1f ",getTemp(data));
        weatherData.temperature = getTemp(data);
        weatherData.tTime = seconds;
        if(noisy){
            fprintf(stderr,"Humidity: %d ", getHumidity(data));
            fprintf(stderr,"\n");
        }
        weatherData.humidity = getHumidity(data);
        weatherData.hTime = seconds;
    }
}
/*
This code is related to dealing with the USB device
*/
// This searches the USB bus tree to find the device
int findDevice(libusb_device **devs)
{
    libusb_device *dev;
    int err = 0, i = 0, j = 0;
    uint8_t path[8]; 
    
    while ((dev = devs[i++]) != NULL) {
        struct libusb_device_descriptor desc;
        int r = libusb_get_device_descriptor(dev, &desc);
        if (r < 0) {
            fprintf(stderr,"Couldn't get device descriptor, %s\n", libusb_strerror(err));
            return(1);
        }

        fprintf(stderr,"%04x:%04x (bus %d, device %d)",
            desc.idVendor, desc.idProduct,
            libusb_get_bus_number(dev), libusb_get_device_address(dev));

        //r = libusb_get_port_numbers(dev, path, sizeof(path));
        //if (r > 0) {
        //  fprintf(stderr," path: %d", path[0]);
        //  for (j = 1; j < r; j++)
        //      fprintf(stderr,".%d", path[j]);
        //}
        fprintf(stderr,"\n");
        
        if (desc.idVendor == VENDOR && desc.idProduct == PRODUCT){
            fprintf(stderr,"Found the one I want\n");
            weatherStation.device = dev;
            return (1);
        }
    }
    return(0);
}

// to handle testing and try to be clean about closing the USB device,
// I'll catch the signal and close off.
void closeUpAndLeave(){
    //OK, done with it, close off and let it go.
    fprintf(stderr,"Done with device, release and close it\n");
    int err = libusb_release_interface(weatherStation.handle, 0); //release the claimed interface
    if(err) {
        fprintf(stderr,"Couldn't release interface, %s\n", libusb_strerror(err));
        exit(1);
    }
    libusb_close(weatherStation.handle);
    libusb_exit(NULL);
    exit(0);
}

// This is where I read the USB device to get the latest data.
unsigned char data[50]; // where we want the data to go
int getit(int whichOne, int noisy){
    int actual; // how many bytes were actually read
    
    // The second parameter is bmRequestType and is a bitfield
    // See http://www.beyondlogic.org/usbnutshell/usb6.shtml
    // for the definitions of the various bits.  With libusb, the 
    // #defines for these are at:
    // http://libusb.sourceforge.net/api-1.0/group__misc.html#gga0b0933ae70744726cde11254c39fac91a20eca62c34d2d25be7e1776510184209
    actual = libusb_control_transfer(weatherStation.handle, 
                    LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_IN,
                    //These bytes were stolen with a USB sniffer
                    0x01,0x0100+whichOne,0,
                    data, 50, 10000);
    if (actual < 0){
        fprintf(stderr,"Read didn't work for report %d, %s\n", whichOne, libusb_strerror(actual));
    }
    else {
        // If you want both of the reports that the station provides,
        // just allow for it.  Right this second, I've found every thing
        // I need in report 1.  When I look further at report 2, this will
        // change
        //fprintf(stderr,"R%d:%d:", whichOne, actual);
        //int i;
        //for(i=0; i<actual; i++){
        //    fprintf(stderr,"%0.2X ",data[i]);
        //}
        //fprintf(stderr,"\n");
        if (whichOne == 1)
            // The actual data starts after the first byte
            // The first byte is the report number returned by 
            // the usb read.
            decode(&data[1], actual-1, noisy);
    }
}
// I do several things here that aren't strictly necessary.  As I learned about
// libusb, I tried things and also used various techniques to learn about the 
// weatherstation's implementation.  I left a lot of it in here in case I needed to
// use it later.  Someone may find it useful to hack into some other device.
int main(int argc, char **argv)
{
    char *usage = {"usage: %s -u -n\n"};
    int libusbDebug = 0; //This will turn on the DEBUG for libusb
    int noisy = 0;       //This will print the packets as they come in
    libusb_device **devs;
    int r, err, c;
    ssize_t cnt;
    
    while ((c = getopt (argc, argv, "unh")) != -1)
        switch (c){
            case 'u':
                libusbDebug = 1;
                break;
            case 'n':
                noisy = 1;
                break;
            case 'h':
                fprintf(stderr, usage, argv[0]);
            case '?':
                exit(1);
            default:
                exit(1);
       }
    fprintf (stderr,"libusbDebug = %d, noisy = %d\n", libusbDebug, noisy);

    if (signal(SIGINT, sig_handler) == SIG_ERR)
        fprintf(stderr,"Couldn't set up signal handler\n"); 
    err = libusb_init(NULL);
    if (err < 0){
        fprintf(stderr,"Couldn't init usblib, %s\n", libusb_strerror(err));
        exit(1);
    }
    // This is where you can get debug output from libusb.
    // just set it to LIBUSB_LOG_LEVEL_DEBUG
    if (libusbDebug)
        libusb_set_debug(NULL, LIBUSB_LOG_LEVEL_DEBUG);
    else
        libusb_set_debug(NULL, LIBUSB_LOG_LEVEL_INFO);

    
    cnt = libusb_get_device_list(NULL, &devs);
    if (cnt < 0){
        fprintf(stderr,"Couldn't get device list, %s\n", libusb_strerror(err));
        exit(1);
    }
    // got get the device; the device handle is saved in weatherStation struct.
    if (!findDevice(devs)){
        fprintf(stderr,"Couldn't find the device\n");
        exit(1);
    }
    // Now I've found the weather station and can start to try stuff
    // So, I'll get the device descriptor
    struct libusb_device_descriptor deviceDesc;
    err = libusb_get_device_descriptor(weatherStation.device, &deviceDesc);
    if (err){
        fprintf(stderr,"Couldn't get device descriptor, %s\n", libusb_strerror(err));
        exit(1);
    }
    fprintf(stderr,"got the device descriptor back\n");
    
    // Open the device and save the handle in the weatherStation struct
    err = libusb_open(weatherStation.device, &weatherStation.handle);
    if (err){
        fprintf(stderr,"Open failed, %s\n", libusb_strerror(err));
        exit(1);
    }
    fprintf(stderr,"I was able to open it\n");
    // There's a bug in either the usb library, the linux driver or the 
    // device itself.  I suspect the usb driver, but don't know for sure.
    // If you plug and unplug the weather station a few times, it will stop
    // responding to reads.  It also exhibits some strange behaviour to 
    // getting the configuration.  I found out after a couple of days of
    // experimenting that doing a clear-halt on the device while before it
    // was opened it would clear the problem.  So, I have one here and a 
    // little further down after it has been opened.
    fprintf(stderr,"trying clear halt on endpoint %X ... ", 0x81);
    err = libusb_clear_halt(weatherStation.handle, 0x81);
    if (err){
        fprintf(stderr,"clear halt crapped, %s  Bug Detector\n", libusb_strerror(err));;
    }
    else {
        fprintf(stderr,"OK\n");
    }
    
    // Now that it's opened, I can free the list of all devices
    libusb_free_device_list(devs, 1); // Documentation says to get rid of the list
                                      // Once I have the device I need
    fprintf(stderr,"Released the device list\n");
    // Now I have to check to see if the kernal using udev has attached
    // a driver to the device.  If it has, it has to be detached so I can
    // use the device.
    if(libusb_kernel_driver_active(weatherStation.handle, 0) == 1) { //find out if kernel driver is attached
        fprintf(stderr,"Kernal driver active\n");
        if(libusb_detach_kernel_driver(weatherStation.handle, 0) == 0) //detach it
            fprintf(stderr,"Kernel Driver Detached!\n");
    }

    int activeConfig;
    err =libusb_get_configuration   (weatherStation.handle, &activeConfig);
    if (err){
        fprintf(stderr,"Can't get current active configuration, %s\n", libusb_strerror(err));;
        exit(1);
    }
    fprintf(stderr,"Currently active configuration is %d\n", activeConfig);

    if(activeConfig != 1){
        err = libusb_set_configuration  (weatherStation.handle, 1);
        if (err){
            fprintf(stderr,"Cannot set configuration, %s\n", libusb_strerror(err));;
            exit(1);
        }
    fprintf(stderr,"Just did the set configuration\n");
    }
    
    err = libusb_claim_interface(weatherStation.handle, 0); //claim interface 0 (the first) of device (mine had jsut 1)
    if(err) {
        fprintf(stderr,"Cannot claim interface, %s\n", libusb_strerror(err));
        exit(1);
    }
    fprintf(stderr,"Claimed Interface\n");
    fprintf(stderr,"Number of configurations: %d\n",deviceDesc.bNumConfigurations);
    struct libusb_config_descriptor *config;
    libusb_get_config_descriptor(weatherStation.device, 0, &config);
    fprintf(stderr,"Number of Interfaces: %d\n",(int)config->bNumInterfaces);
    // I know, the device only has one interface, but I wanted this code
    // to serve as a reference for some future hack into some other device,
    // so I put this loop to show the other interfaces that may
    // be there.  And, like most of this module, I stole the ideas from
    // somewhere, but I can't remember where (I guess it's google overload)
    const struct libusb_interface *inter;
    const struct libusb_interface_descriptor *interdesc;
    const struct libusb_endpoint_descriptor *epdesc;
    int i, j, k;
    for(i=0; i<(int)config->bNumInterfaces; i++) {
        inter = &config->interface[i];
        fprintf(stderr,"Number of alternate settings: %d\n", inter->num_altsetting);
        for(j=0; j < inter->num_altsetting; j++) {
            interdesc = &inter->altsetting[j];
            fprintf(stderr,"Interface Number: %d\n", (int)interdesc->bInterfaceNumber);
            fprintf(stderr,"Number of endpoints: %d\n", (int)interdesc->bNumEndpoints);
            for(k=0; k < (int)interdesc->bNumEndpoints; k++) {
                epdesc = &interdesc->endpoint[k];
                fprintf(stderr,"Descriptor Type: %d\n",(int)epdesc->bDescriptorType);
                fprintf(stderr,"Endpoint Address: 0x%0.2X\n",(int)epdesc->bEndpointAddress);
                // Below is how to tell which direction the 
                // endpoint is supposed to work.  It's the high order bit
                // in the endpoint address.  I guess they wanted to hide it.
                fprintf(stderr," Direction is ");
                if ((int)epdesc->bEndpointAddress & LIBUSB_ENDPOINT_IN != 0)
                    fprintf(stderr," In (device to host)");
                else
                    fprintf(stderr," Out (host to device)");
                fprintf(stderr,"\n");
            }
        }
    }
    fprintf(stderr,"trying clear halt on endpoint %X ... ", (int)epdesc->bEndpointAddress);
    err = libusb_clear_halt(weatherStation.handle, (int)epdesc->bEndpointAddress);
    if (err){
        fprintf(stderr,"clear halt crapped, %s  SHUCKS\n", libusb_strerror(err));;
        closeUpAndLeave();
    }
    else {
        fprintf(stderr,"OK\n");
    }

    // So, for the weather station we now know it has one endpoint and it is set to
    // send data to the host.  Now we can experiment with that.
    //
    // I don't want to just hang up and read the reports as fast as I can, so
    // I'll space them out a bit.  It's weather, and it doesn't change very fast.
    int tickcounter= 0;
    while(1){
        sleep(1);
        if(tickcounter++ % 10 == 0){
            getit(1, noisy);
        }
        if(tickcounter % 30 == 0){
            getit(2, noisy);
        }
        if (tickcounter % 15 == 0){
            showit();
        }
    }
}
Yes, I'll get it up on github so you can download it, I just want to finish this series before I put it there.  This compiles like the last example where we had to specify the library location:

cc -o weatherstation  weatherstation.c -L/usr/local/lib -lusb-1.0

I called it weatherstation, if you want a shorter name, feel free.  If you take a quick glance at the code you'll notice that it accepts parameters:

weatherstation -[unh]

-u will turn on libusb debug so you can see what is happening behind the scenes.
-n (noisy) will print the various decoded sensor data as it comes in
-h simple help for the options because I won't remember them

The output of the code looks like this combining without parameters:

libusbDebug = 0, noisy = 0
24c0:0003 (bus 1, device 6)
Found the one I want
got the device descriptor back
I was able to open it
trying clear halt on endpoint 81 ... OK
Released the device list
Currently active configuration is 1
Claimed Interface
Number of configurations: 1
Number of Interfaces: 1
Number of alternate settings: 1
Interface Number: 0
Number of endpoints: 1
Descriptor Type: 5
Endpoint Address: 0x81
 Direction is  In (device to host)
trying clear halt on endpoint 81 ... OK
{"windSpeed":{"WS":"0.0","t":"1417653478"},"windDirection":{"WD":"NNW","t":"0"},"temperature":{"T":"62.6","t":"1417653478"},"humidity":{"H":"92","t":"1417653478"},"rainCounter":{"RC":"0","t":"0"}}
{"windSpeed":{"WS":"3.7","t":"1417653488"},"windDirection":{"WD":"NNE","t":"1417653488"},"temperature":{"T":"62.6","t":"1417653478"},"humidity":{"H":"92","t":"1417653478"},"rainCounter":{"RC":"12","t":"1417653488"}}
^CShutting down ...
Done with device, release and close it

Notice that you get the informational stuff and errors will show up also, but if you use a redirect to get rid of the stderr output, it will look like this:

pi@deserthome2:~/src$ weatherstation 2>/dev/null
{"windSpeed":{"WS":"0.0","t":"1417653739"},"windDirection":{"WD":"SSE","t":"1417653739"},"temperature":{"T":"62.6","t":"1417653729"},"humidity":{"H":"92","t":"1417653729"},"rainCounter":{"RC":"12","t":"1417653739"}}
{"windSpeed":{"WS":"0.0","t":"1417653749"},"windDirection":{"WD":"SSE","t":"1417653749"},"temperature":{"T":"62.6","t":"1417653729"},"humidity":{"H":"92","t":"1417653729"},"rainCounter":{"RC":"12","t":"1417653749"}}
{"windSpeed":{"WS":"0.0","t":"1417653769"},"windDirection":{"WD":"SSE","t":"1417653749"},"temperature":{"T":"62.6","t":"1417653769"},"humidity":{"H":"92","t":"1417653769"},"rainCounter":{"RC":"12","t":"1417653749"}}

Notice that I used the line:

weatherstation 2>/dev/null

This will throw away any information or error, so you may want to send it to a file instead:

weatherstation 2>somefilename

But you can read up on that if you don't already have it down pat.  The JSON string has each device, its value and the time it was read.  So, suppose you pipe this into a little piece of python code that looks like this:

#!/usr/bin/python
import sys
import json
import time

buff = ''
while True:
    try:
        buff += sys.stdin.read(1)
        if buff.endswith('\n'):
            data = json.loads(buff[:-1])
            print "On", time.strftime("%A, %B, %d at %H:%M:%S",time.localtime(float(data["windSpeed"]["t"]))),
            print "the wind was blowing from the", data["windDirection"]["WD"],
            print "at\n", data["windSpeed"]["WS"], "mph,",
            print "and it is", data["temperature"]["T"], "degrees F Outside.",
            print "The humidity is", data["humidity"]["H"], "percent",
            print "and \nthe rain counter is", data["rainCounter"]["RC"],
            print
            sys.stdout.flush()
            buff = ''
    except KeyboardInterrupt:
        sys.stdout.flush()
        sys.exit()

Then you'll get a cute little update that looks like this:

On Wednesday, December, 03 at 17:51:01 the wind was blowing from the E at
0.0 mph, and it is 62.6 degrees F Outside. The humidity is 92 percent and
the rain counter is 12
On Wednesday, December, 03 at 17:51:21 the wind was blowing from the ENE at
2.5 mph, and it is 62.6 degrees F Outside. The humidity is 92 percent and
the rain counter is 12
On Wednesday, December, 03 at 17:51:31 the wind was blowing from the ENE at
2.5 mph, and it is 62.6 degrees F Outside. The humidity is 92 percent and
the rain counter is 12
On Wednesday, December, 03 at 17:51:51 the wind was blowing from the ENE at
0.0 mph, and it is 62.6 degrees F Outside. The humidity is 92 percent and
the rain counter is 12

I called the little python script 'readit.py', so the command looked like this:

 weatherstation 2>/dev/null | readit.py

Yes, I threw away the information and errors, trust me, when I run this for real, I'll be saving that stuff in /var/log/house and running logrotate on it.

There it is, the whole project to get the data, and do something with it.  Are you starting to understand why I cut it up into pieces?  You just had a primer on breaking into devices that they've been keeping secret, creating your very own USB driver for a new device in user space, using udev to create a custom file mount point specific to a vendor device, creating a piece of code that pipes its output so other pieces of code can work on it further, building a simple python script to format the data, did I forget anything?  Not bad for three settings.

Now, go forth and conquer.

Part 4 of this project is here <link>.

Monday, December 1, 2014

AcuRite Weather Station, Raspberry Pi and a USB interface; Part 2

In part 1 <link> I described the AcuRite weather station I have and how I wanted to hook into the USB interface so I could have the data and do anything I wanted to with it.  I also gave instructions on how to modify certain files such that you have a device that a user can read.  Now, we have to hook some code to the USB port and actually deal with the device.  But first, we have to get a library that will help us along.

From my research, I only found one source that had the things I needed and enough documentation to actually stand a chance of implementing it, libusb.  Libusb is a multiplatform library to interface to a USB device from user space.  Let's discuss this a tiny bit since there are better sites out there to get this information from, but you need a tiny bit of information to understand what comes next.

On unix, there's users and the kernal.  The kernal is the ultimate controller of everything; it has access to all the facilities and takes care of portioning them out and keeping track of what goes on.  Users can't mess with the things the kernal controls, and that's a good thing.  We can't kill ourselves and have to restore the system from backup if we stay away from the things the kernal should be dealing with.  Unfortunately, the USB buss belongs to the kernal.  So, we need to use kernal interface routines to control the USB devices and those are really hard to understand.  Libusb gives us access to those interfaces in a reasonably simpler (not simple) way.  USB is very complex and nothing can make it easy, but it can, at least, be possible.

So where do we get libusb?  It's already on the machine, but the version there is several generations back and hard to link to.  I searched around and found out a few things that can help you keep from being as confused as I was when I first started this.

1. Almost all the web sites out there deal with a version of libusb that is outdated.  Libusb has gone through several changes and massive improvement this year (2014) and most sites don't work with the newest stuff.

2. As always, the newest stuff is more stable and has greater capabilities.

3.  In your searching, you'll run across libusb and libusbx.  These are two different things; libusbx is a fork of libusb that extended certain things.  The two were merged back together to form a new libusb that serves both projects.  So, web sites that talk about one of them, probably won't work with the latest.

4. You can't automatically install the latest libusb.  The packages on the Raspberry Pi are behind several versions.

5. Using the latest version, lots of the examples on the web won't even compile.

6. You WANT the latest version.

This all means you will have to get the latest version and compile it yourself.  There are two ways of doing this: get a download from the libusb website and install it, or get the source from github and install that.  I did both and they both worked, but I decided the best way for me is to grab it from github, because I know that represents what I want right now.  This may not be true in a couple of months, so consider a download from the libusb home site.

I used libusb-1.0.19 as my base.  You can get it from their (new) home site <link> , just click on 'download' and choose latest.  In a few weeks, this might not get you this version, so decide if you want the latest (I would) or if you want this one.  If you chose to go with 1.0.19, it'll be under the 'Previous Releases' area.  Download it and put it on the Pi somewhere.  Now you have to compile it.

You'll have to extract the sources; just use tar xvf filename and watch it fly by.  Then go into the new directory and read the file 'INSTALL'.  This is where they keep the build instructions, and for me, it was a simple configure, make, make install sequence and everything was done.  However, there's two caveats to this.  Make sure your Pi is up to date and install libudev-dev.

I had restored my Pi from backup first and the backup was from a while ago.  This meant that the software on the machine was months behind.  This gave me no end of trouble which I fixed by simply updating the machine:

sudo apt-get update
sudo apt-get upgrade

The trick to this is to do the update about 30 minutes before bedtime, then do the upgrade just before going to bed.  I don't know how long the upgrade took, I was asleep when it finished, but it took a long time.  You don't want to watch it, it's boring.  There is a confirmation prompt though, so wait for it to ask if you really want to do this, answer yes, and go to bed.  This works by the update going out and reading the various pacakages to see what the very latest is; it doesn't actually change anything except that.  The upgrade actually gets the new stuff and installs it.  So, the upgrade may have a whole lot to do and could take a while.

So, update, upgrade, get libusb but don't compile it.  You're still not ready.  Now you need to get libudev-dev.  This is an interface to the support functions that udev provides (remember udev from the last post?), and libusb needs them.  Fortunately, you can use apt-get to grab these:

sudo apt-get install libudev-dev

So, are you finally ready to compile libusb?  After reviewing the instruction in INSTALL in the directory you got with the release, go ahead and make the library.  However, be sure to sudo each step of the process.  If you don't errors happen and with the huge amount of stuff printed, it's real easy to miss something.  For me, it was:

sudo ./configure
sudo make
sudo make install

Now you're finally ready to actually do something.  Libusb is a 'c' library, so I used c code to deal with it.  I know, python is my usual preference, but when in Rome ...  Remember, most of the examples on the web won't work with the latest libusb, so it was a bit tough getting things to work.  I finally had to just step through some of them a line at a time and adapt them to work with the latest.  It was a royal pain, but it did help me to learn a lot about what I was doing.

So, here's the first code to deal with the USB device that we're going to eventually use:

usbexample1.c
/*
    Experimentation with a USB interface to the Acu-Rite 5 in 1 Weatherstation
    specifically for the Raspberry Pi.
    
    Because there likely to be a version of libusb and the associated header file
    on a Pi, use the command line below to build it since the build of libusb-1.0.19
    places things in /usr/local
    
    cc usbexample1.c -L/usr/local/lib -lusb-1.0    
    use ldd weatherstation to check which libraries are linked in.
    If you still have trouble with compilation, remember that cc has a -v
    parameter that can help you unwind what is happening.
*/

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <sys/time.h>
#include <libusb-1.0/libusb.h>

// The vendor id and product number for the AcuRite 5 in 1 weather head.
#define VENDOR 0x24c0
#define PRODUCT 0x0003

// I store things about the weather device USB connection here.
struct {
    libusb_device *device;
    libusb_device_handle *handle;
    int verbose;
} weatherStation;

/*
This code is related to dealing with the USB device
*/
// This searches the USB bus tree to find the device
int findDevice(libusb_device **devs)
{
    libusb_device *dev;
    int err = 0, i = 0, j = 0;
    uint8_t path[8]; 
    
    while ((dev = devs[i++]) != NULL) {
        struct libusb_device_descriptor desc;
        int r = libusb_get_device_descriptor(dev, &desc);
        if (r < 0) {
            fprintf(stderr,"Couldn't get device descriptor, %s\n", libusb_strerror(err));
            return(1);
        }

        fprintf(stderr,"%04x:%04x (bus %d, device %d)",
            desc.idVendor, desc.idProduct,
            libusb_get_bus_number(dev), libusb_get_device_address(dev));

        //r = libusb_get_port_numbers(dev, path, sizeof(path));
        //if (r > 0) {
        //  fprintf(stderr," path: %d", path[0]);
        //  for (j = 1; j < r; j++)
        //      fprintf(stderr,".%d", path[j]);
        //}
        fprintf(stderr,"\n");
        
        if (desc.idVendor == VENDOR && desc.idProduct == PRODUCT){
            fprintf(stderr,"Found the one I want\n");
            weatherStation.device = dev;
            return (1);
        }
    }
    return(0);
}

// to handle testing and try to be clean about closing the USB device,
// I'll catch the signal and close off.

// I do several things here that aren't strictly necessary.  As I learned about
// libusb, I tried things and also used various techniques to learn about the 
// weatherstation's implementation.  I left a lot of it in here in case I needed to
// use it later.  Someone may find it useful to hack into some other device.
int main(void)
{
    libusb_device **devs;
    int r, err;
    ssize_t cnt;

    err = libusb_init(NULL);
    if (err < 0){
        fprintf(stderr,"Couldn't init usblib, %s\n", libusb_strerror(err));
        exit(1);
    }
    // This is where you can get debug output from libusb.
    // just set it to LIBUSB_LOG_LEVEL_DEBUG
    libusb_set_debug(NULL, LIBUSB_LOG_LEVEL_INFO);
    
    cnt = libusb_get_device_list(NULL, &devs);
    if (cnt < 0){
        fprintf(stderr,"Couldn't get device list, %s\n", libusb_strerror(err));
        exit(1);
    }
    // got get the device; the device handle is saved in weatherStation struct.
    if (!findDevice(devs)){
        fprintf(stderr,"Couldn't find the device\n");
        exit(1);
    }
    // Now I've found the weather station and can start to try stuff
    // So, I'll get the device descriptor
    struct libusb_device_descriptor deviceDesc;
    err = libusb_get_device_descriptor(weatherStation.device, &deviceDesc);
    if (err){
        fprintf(stderr,"Couldn't get device descriptor, %s\n", libusb_strerror(err));
        exit(1);
    }
    fprintf(stderr,"got the device descriptor back\n");
    
    // Open the device and save the handle in the weatherStation struct
    err = libusb_open(weatherStation.device, &weatherStation.handle);
    if (err){
        fprintf(stderr,"Open failed, %s\n", libusb_strerror(err));
        exit(1);
    }
    fprintf(stderr,"I was able to open it\n");
    // Now that it's opened, I can free the list of all devices
    libusb_free_device_list(devs, 1); // Documentation says to get rid of the list
                                      // Once I have the device I need
    fprintf(stderr,"Released the device list\n");
    // Now I have to check to see if the kernal using udev has attached
    // a driver to the device.  If it has, it has to be detached so I can
    // use the device.
    if(libusb_kernel_driver_active(weatherStation.handle, 0) == 1) { //find out if kernel driver is attached
        fprintf(stderr,"Kernal driver active\n");
        if(libusb_detach_kernel_driver(weatherStation.handle, 0) == 0) //detach it
            fprintf(stderr,"Kernel Driver Detached!\n");
    }
    int activeConfig;
    err =libusb_get_configuration   (weatherStation.handle, &activeConfig);
    if (err){
        fprintf(stderr,"Can't get current active configuration, %s\n", libusb_strerror(err));;
        exit(1);
    }
    fprintf(stderr,"Currently active configuration is %d\n", activeConfig);

    err = libusb_set_configuration  (weatherStation.handle, 1);
    if (err){
        fprintf(stderr,"Cannot set configuration, %s\n", libusb_strerror(err));;
        exit(1);
    }
    fprintf(stderr,"Just did the set configuration\n");

    
    err = libusb_claim_interface(weatherStation.handle, 0); //claim interface 0 (the first) of device (mine had jsut 1)
    if(err) {
        fprintf(stderr,"Cannot claim interface, %s\n", libusb_strerror(err));
        exit(1);
    }
    fprintf(stderr,"Claimed Interface\n");
    fprintf(stderr,"Number of configurations: %d\n",deviceDesc.bNumConfigurations);
    struct libusb_config_descriptor *config;
    libusb_get_config_descriptor(weatherStation.device, 0, &config);
    fprintf(stderr,"Number of Interfaces: %d\n",(int)config->bNumInterfaces);
    // I know, the device only has one interface, but I wanted this code
    // to serve as a reference for some future hack into some other device,
    // so I put this loop to show the other interfaces that may
    // be there.  And, like most of this module, I stole the ideas from
    // somewhere, but I can't remember where (I guess it's google overload)
    const struct libusb_interface *inter;
    const struct libusb_interface_descriptor *interdesc;
    const struct libusb_endpoint_descriptor *epdesc;
    int i, j, k;
    for(i=0; i<(int)config->bNumInterfaces; i++) {
        inter = &config->interface[i];
        fprintf(stderr,"Number of alternate settings: %d\n", inter->num_altsetting);
        for(j=0; j < inter->num_altsetting; j++) {
            interdesc = &inter->altsetting[j];
            fprintf(stderr,"Interface Number: %d\n", (int)interdesc->bInterfaceNumber);
            fprintf(stderr,"Number of endpoints: %d\n", (int)interdesc->bNumEndpoints);
            for(k=0; k < (int)interdesc->bNumEndpoints; k++) {
                epdesc = &interdesc->endpoint[k];
                fprintf(stderr,"Descriptor Type: %d\n",(int)epdesc->bDescriptorType);
                fprintf(stderr,"Endpoint Address: 0x%0.2X\n",(int)epdesc->bEndpointAddress);
                // Below is how to tell which direction the 
                // endpoint is supposed to work.  It's the high order bit
                // in the endpoint address.  I guess they wanted to hide it.
                fprintf(stderr," Direction is ");
                if ((int)epdesc->bEndpointAddress & LIBUSB_ENDPOINT_IN != 0)
                    fprintf(stderr," In (device to host)");
                else
                    fprintf(stderr," Out (host to device)");
                fprintf(stderr,"\n");
            }
        }
    }
    //OK, done with it, close off and let it go.
    fprintf(stderr,"Done with device, release and close it\n");
    err = libusb_release_interface(weatherStation.handle, 0); //release the claimed interface
    if(err) {
        fprintf(stderr,"Couldn't release interface, %s\n", libusb_strerror(err));
        exit(1);
    }
    libusb_close(weatherStation.handle);
    libusb_exit(NULL);
    exit(0);
}

Remember I told you that the installation process of libusb puts it in /usr/local?  Well, it's a little bit worse than that.  The actual directory it puts the header file libusb.h in is /usr/local/include/libusb-1.0 which makes it a bit more complicated to deal with.  In the source file above I used the line:

#include <libusb-1.0/libusb.h>

They also put the libraries we need in the directory /usr/local/lib, which is pretty normal.  These things make the command line to the compiler:

cc  usbexample1.c -L/usr/local/lib -lusb-1.0

because the compiler defaults to look in usr/local/include for the libusb.h file, but you have to get specific for the library.  Pay attention here, I fought this for a while because I was picking up the wrong library and some symbols weren't defined.    If you get into trouble and just can't figure out what is going wrong, the compiler has a -v flag that will print what it's doing step by step.  Carefully read this output and you should see where you're having a problem.  It outputs a lot of data though, so it isn't easy.

cc  -v usbexample1.c -L/usr/local/lib -lusb-1.0

Once you get it to compile, you'll be left with an a.out file that you can run to see what happens.  Just type in ./a.out and look at the output:

pi@deserthome:~/src$ ./a.out
24c0:0003 (bus 1, device 4)
Found the one I want
got the device descriptor back
I was able to open it
Released the device list
Currently active configuration is 1
Just did the set configuration
Claimed Interface
Number of configurations: 1
Number of Interfaces: 1
Number of alternate settings: 1
Interface Number: 0
Number of endpoints: 1
Descriptor Type: 5
Endpoint Address: 0x81
 Direction is  In (device to host)
Done with device, release and close it

This tells you that the code was able to find the device based on the vendor and id number, then through various arcane machinations, it was able to open it.  Then it just released it and closed it.

I put a lot of comments in the file, so you should be able to follow along and use the libusb api documentation <link> to understand it more fully.  Also notice that I sent all output to stderr.  For those of you that didn't understand that, Linux has both stderr and stdout.  Stdout is the normal place you send text you want to read and stderr is for errors.  There's a good reason for this, and I'll tell you all about it in the next post.

I will have the code above available on github soon, I just haven't done it yet.  So, if you're in a hurry, do a little copy and paste to create the file.  This code can also serve as a current (sort of, since things change pretty quickly) example of how to get to and reserve a USB device.  In the next installment I get more specific to the weather station.

So now we have the Pi set up with a USB device that is hooked to the weather station and some code that can attach to it and open it.  Next we have to read the data and decode it.

Have fun.

Part three of this series is here <link>.