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.

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>.

Sunday, November 30, 2014

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

This is a long drawn out story.  Sorry.  I'm going to go into more detail than I usually do and the project will span more than one blog entry.  That's because it is really complex and deals with stuff that most of us would rather not mess with.  We want to monitor and control our house, not do a dissertation.  But, in this case, you can't have enough background information or advice.  There are things that can happen that you need a minimum of background to overcome.  I hope to give you enough to repeat the project.

It all started when one of my readers asked me if I had looked into a weather station for the house. Well, I had, but there were two problems, the good ones cost a fortune, and the others don't have a lot of capabilities. I was going to buy one of the sensor sets sold by SparkFun. They have a really nice one for about $70 that would do the job pretty well. As luck would have it, I was in Costco drooling over the multi-terrabyte disks and noticed they had a Acu-Rite weather station for $80, display and all. It had a sensor head with windspeed, rainfall, temperature, humidity and wind direction. The console was an LCD color display with a USB output that promised I could plug it into a computer and send data out on the web.


Yes, I left with it.

Heck, I couldn't buy the sensors and assemble them any cheaper, plus I'd have to learn all about reading rain gauges and anemometers.  This sounded like a great deal, and it was.  There was only one thing missing, being able to get the data into some kind of database so I could play with it.  Yes there was some pretty slick software that would upload the data to the AcuRite site, but you all know how I feel about relying on someone else for my data.  Also, the software ONLY runs on a Windows PC.  That means I have to keep a PC running all the time to get the data uploaded.  What?  This is the 21st century people, what's up with keeping a big old power hungry PC running 24/7?

I went looking for solutions and found a couple of interesting things.  A site called Weather Display <link> that had some nice software and a driver for Linux, but the poor developer had recently lost the source to his work <link>, and he wanted $70 bucks for it.  Another one, Valley Information Systems <link> who, it turns out, does the driver for the AcuRite software for the weather station.  V.I.S. had a forum as well <link>, so I prowled it and found out that they didn't have a Linux driver, and basically weren't interested in doing one.  I got this from a long rambling discussion with the author (you know me, I can't shut up) where he flat out told me it wasn't worth his time.  I spent several more hours looking for leads on a Linux driver with no luck at all.

So, I didn't want to spend $70 on a system that the source was being rebuilt from scratch for, and I didn't want to run it on a PC since that would tie up my machine and hook me to a wire 24/7; I guess the only solution was to write something myself.

But, what to do?  I could get a receiver and capture the RF sent from the 5 in 1 sensor I mounted on the roof:


(people tell me it looks like a rabbit setting up there)

I could also buy one of their 'bridge' devices.  This is basically an RF receiver hooked to an ethernet plug through some kind of processor.  This device was listed at $80, the same price as the weather station, and it wasn't even wireless.
It did meet the requirement of not having to have a PC plugged in and tethered to the display though.  I guess it's AcuRite's way of overcoming an obvious shortcoming ... for a price.

Or, I could try and conquer the USB interface to it on my Raspberry Pi.  Since I have years of experience dealing with RF in various forms, what do you think I did?  Right, I decided to hack into the USB interface.

The only problem is that I know exactly zero about USB and how to interact with it ... sigh; here I go again.

A while later, I had learned that there were several ways to interface with USB on the Pi, something called pyusb written in python, but there was so little documentation on it that frankly, I couldn't make heads or tails out of it.  There was libusb that had tons of documentation, but all of it was written in a language I couldn't understand --- USB jargonese.  There was something called libusbx, but it looked exactly like libusb.  There were a ton of examples, but nothing that showed how you could actually get data off the darn thing.  If I wanted to list the devices, I could find an example almost instantly, but actually get some data, basically nothing.

Fine, first I'd play with the USB, and get a feel for what was going on.  Maybe that would give me a few keywords that I could use in a better refined google search.  I learned all about lsusb and several other tools that can enumerate the devices hooked to a machine.  I looked at USB sniffers and how they worked.  I found out that HID stood for Human Interface Device and it had a special driver that was dealt with differently than other USB devices.  I learned all about how to modify udev rules to make a touchpad behave like a joystick; everything except how to get data from this weather station.

I decided to just start playing and see what happened.  I plugged the weather station into the Pi and started looking around.  The very first thing I found out was that a normal user on the Pi, like the user pi, can't read the device that is created when you plug the weather station in.  You can tell if this is the problem; look for the device in /dev and check its permissions

crw------- 1 root root 249, 0 Dec 31  1969 /dev/hidraw0

See, what happens is that the device isn't recognized by udev (the process that watches the USB bus) and it assigned the hidraw driver (the most basic generic driver of this type) to the device.  The hidraw driver defaulted like this can only be read and written to by root.  The situation was that the device would identify itself as a HID device, but it wasn't recognized properly and udev would default to the hidraw device driver.  OK, I started looking for how to use this device.  It turns out there's quite a bit of information on the hidraw driver and how to use it.  So I put together some code and gave it a try.

Nothing worked as advertised.  Nothing.  The weather station might have been assigned the hidraw driver, but it wouldn't act like one to Linux.  That was a bust.  I wrote some code to read the various descriptors from the device and noticed a couple of errors in its implementation that might have been causing the problem, but nothing that could solve the problem.  All of this was running as root on the Pi since a normal user couldn't talk to the device.  Yes, I could change the permissions on the device, but that would only last until it was unplugged.  When the device was plugged back in, the device was created root-only all over again.  I could also set the bit such that the code would run as root, but that could create a monster if I messed up in the code.  I really didn't want to risk that.  And, I was getting tired of dealing with the problems of a default device that wouldn't work, so I decided to conquer that item first.

When dealing with USB, the devices can come and go at will.  When we unplug the device, the system notices it and dismantles the connection to it.  Everything is restored when the device is plugged back in.  Although, the device may come up with a different mount point which means the device name changes.  For example on my Pi, since I don't have a keyboard or mouse, the weather station shows up as hidraw0.  On some other machine it could be hidraw1, hidraw2, whatever the next number after the devices already plugged in happens to be.  That's just the nature of USB.  This stuff works by a set of rules that are in the secret directory /etc/udev/rules.d.

I'm not going to drive you nuts with an in-depth discussion of udev and how it works, but I will tell you how to fix the permission problems so the device can be read by a normal Linux user (not root).

First, you have to find out what the OS sees the device as.  This is logged in the system log when you plug the device in and udev does its thing.  So, plug the weather station into the Pi and do the command 'dmesg'. You're interested in a device from Chaney Instrument, so look in the dmesg output for something like the following:
[    2.399920] usb 1-1.2: new low-speed USB device number 4 using dwc_otg
[    2.536425] usb 1-1.2: New USB device found, idVendor=24c0, idProduct=0003
[    2.538128] usb 1-1.2: New USB device strings: Mfr=0, Product=2, SerialNumber=0
[    2.541669] usb 1-1.2: Product: Chaney Instrument
[    3.127499] udevd[156]: starting version 175
[    5.553287] bcm2708-i2s bcm2708-i2s.0: Failed to create debugfs directory
[   12.559829] hid-generic 0003:24C0:0003.0001: usb_submit_urb(ctrl) failed: -1
[   12.561497] hid-generic 0003:24C0:0003.0001: timeout initializing reports
[   12.563452] input: Chaney Instrument as /devices/platform/bcm2708_usb/usb1/1-1/1-1.2/1-1.2:1.0/input/input0
[   12.569076] hid-generic 0003:24C0:0003.0001: input,hidraw0: USB HID v1.11 Device [Chaney Instrument] on usb-bcm2708_usb-1.2/input0
I used the command 'dmesg | grep -i usb' to capture this, and it means the HID USB driver grabbed the device because it is listed as type 3 (HID) in its descriptor.

Edit: A short time after I wrote this, I found a significant problem.  It's all detailed in a later post (I'm working on it right now).  Net, DON'T CHANGE THE COMMAND LINE.  It won't destroy your machine or anything, but it will make the weather station USB interface fail in a really annoying fashion.  Skip down to the description of the udev changes and go on from there

That needs to be fixed, but first a tiny bit about cmdline.txt.  This is used to feed parameters into the Linux boot up process, for example many folk have a cmdline.txt file like this when they first boot their Pi:

dwc_otg.lpm_enable=0 console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait

My Pi, before this change looks like this:

dwc_otg.lpm_enable=0 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait

I had to remove the console entries for other projects, so the command is smaller.  To stop the HID driver binding to the weather station USB port from happening, add this phrase to /boot/cmdline.txt, but be careful, messing up this line may cause the Pi not to boot back up.

usbhid.quirks=0xXXXX:0xYYYY:0x04

Replace XXXX with the idVendor above and the YYYY with the idProduct above, the 0x04 is HID_QUIRK_IGNORE, leave that alone. Just put the phrase somewhere in the line, you'll see what I mean when you look at it.  It seems the HID driver has allowances for 'quirks', things that just don't work the way you expect them to - like this device.  After I made the changes, my /boot/cmdline.txt looks like this:

dwc_otg.lpm_enable=0 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline usbhid.quirks=0x24c0:0x0003:0x04 rootwait

Edit: This is where you want to pick up again.  Yes, I could have just edited this out and gone on with life, but that isn't fair.  We all make mistakes and admitting them and correcting for it is part of the fun.

Now you need to make a new usb device usable by someone other than root for the weather station, so go to the directory /etc/udev/rules.d (remember that from above) and create a file named 10-local.rules which contains the line:

SUBSYSTEM=="usb", ATTRS{idVendor}=="XXXX", ATTRS{idProduct}=="YYYY", SYMLINK="weather" MODE="666",GROUP="users"

The reason for the obscure file name is due to the way the file is read. The XXXX and YYYY are replaced with the idVendor and idProduct as described above. This will cause udev to make the device usable by a normal user.   My /etc/udev/rules.d/10-local.rules looks like this:

SUBSYSTEM=="usb", ATTRS{idVendor}=="24c0", ATTRS{idProduct}=="0003", SYMLINK="weather" MODE="666",GROUP="users"

Yours will probably look exactly the same since the vendor and product number won't change.  This rule will set the permission and create a symbolic link in the /dev/directory called 'weather' for the station.  Be careful with the equal and double equal signs; one is a comparison and the other is an assignment.  I fought this mistake for a hour before I noticed what I did wrong.  After you reboot the machine to make it read the changes, take a look at /dev/weather:

lrwxrwxrwx 1 root root 15 Dec 31  1969 /dev/weather -> bus/usb/001/004

The permissions are right and the link is into the USB bus structure.  To test that a normal user can get to the file, do 'hexdump < /dev/weather' while logged in as pi and make sure you get something on the screen.  The reason for hexdump is to prevent control characters from getting to the screen and messing up settings.  You're not really interested in what the data is, just that you can get it.

Once all these usb related changes are done you're all set with an entry in /dev called weather that can be read and written by a normal user.  You can unplug the device and /dev/weather will disappear; plug it back in and /dev/weather will come back.  This is the way it's supposed to work.  I actually did a 'tail -f  /var/log/messages' and watched as I plugged and unplugged the weather station.  It had taken a lot of research and experimentation to get to this point, and I wanted to enjoy it.  DO NOT DO THIS!  I now have a plug on the back of the weather station console that is flaky.  It's ok to unplug it, but don't be like me.

Edit: Note that the hidraw device will still be there, that's a good thing and is explained in a later post.  The new device 'weather' is also there and the permissions are necessary.

So, when do we get to the part about actually reading the darn thing.  Maybe in the next post, but there's much more to come.  We have to get libusb sorted out; that's a story in itself and will probably bore you to tears.  Then we have to discuss how to actually implement a user-space-usb-reader.  That's part of the jargon that I picked up learning about this.  Eventually, we'll actually decode the bit stream the weather station provides; yes, it's a dog gone bitstream.

I promise, I won't take months and months to get the rest of it up, but I don't want to make these too long.

Part 2 of this is now here <link>