Showing posts with label AcuRite 5N1. Show all posts
Showing posts with label AcuRite 5N1. Show all posts

Wednesday, March 9, 2016

My Weather Station is Back Online

Yep, I got it working. About a week ago my weather station Raspberry Pi died, and predictably, I couldn't get it back up without major work <link>. It's all better now running on a Raspberry Pi model B. Yes, I know that isn't the latest of the Pi series, but I had it already, OK.

The Raspbian Jessie booted to a graphic user interface directly, not to a command line. This was a bit of a surprise because I usually bring up the Pi's I have headless so I don't have to hunt down enough equipment to bring them up on my TV. But, since I had the stuff handy from trying to get it working before I gave up and just reloaded, it came up and I configured it, then stopped the GUI. Loading the GUI makes the boot sequence much longer and I usually just use putty on a laptop to control these machines, I don't need it.

Contrary to many posts out there, the configuration screens worked just fine. I was able to assign a static IP address and finish the configuration easily. There was a little hunting through the various menus and icons to find things, but that's part and parcel of any first time installation.

But installing my stuff turned out to be somewhat harder than I expected because, as usual, every piece of supporting software had changed and I had to hunt down what I needed. I used the latest (as of the date on this post) version of libusb, it was 1.0.20, which was several versions later than the one that I would get from a normal apt-get on the Pi.

This version worked well and had a few 'gotchas's' in it that I had to stumble through to get it to work. There was enough documentation and forum postings out there that I found to help me through the process.

I also used the latest rtl-sdr. It was pretty easy as well, but also had a couple of things that I had to chase down. Then I loaded the version of rtl_433 that I had set aside so I wouldn't have to keep making my changes in that code. With the poking around I did in libusb and rtl-sdr, this piece was super simple.

I hooked them all together by starting things on the command line and it worked well. I was able to read the weatherhead RF signal and save it off to my database. The interface I have to move the data around for viewing worked just as it had before. The only thing left was to implement start up  and restart using systemd instead of upstart.

For those of you that wonder why I needed all this, let me describe what's going on a bit. The usb implementation on the Pi is kernal based and not easily modified by the user. That means I had to get libusb and install it. libusb is a user space interface to the usb capabilities on Linux. Then, I had to be able to read one of those sdr radios so I could find the RF signal from the AcuRite weatherhead. I posted about this earlier <link>. Now that I had the signal, decoding it requires rtl_433 which is a piece of software that was developed to decode the signals of many of the wireless devices available such as thermostats, temperature sensors, door bells, weatherheads, etc.

So, it's software piece piled on software piece piled on ... Sure, I could have incorporated all the things I needed into one piece of software and just ran it, but that's not what leveraging software is all about. You use the piece that does the job you need and add special tweaks specific to your implementation. That saves time and doesn't cost much if anything.

With it working and the only piece left being restarting the processes, I dived into systemd. First though, a tiny bit of background. Unix (and linux came from unix regardless of what people say) has to have something to get it going. This used to be the init process. Init would load the various pieces of software and handle how they interacted. Over time init was replaced with various other things that had more features or less bugs. As is rampant in unix and its derivatives, everyone hated something about each of them. Raspberry Pi uses an operating system called 'Raspbian' which is Debian modified to work specifically on the Pi. There are a ton of other operating systems and the choice of which one to actually use is totally up to the person that installs it. I chose to use Raspian because it was the one the folk that produced the Pi chose to supply. I figured it would have the most support and documentation of various forms over time.

The Raspian that I installed with my first Pi used Sys-V init. This system five init is really old and complex to use. I hate complex, so I replaced it with the clearly superior init process call 'upstart'. Upstart is cool and easy to use once you install it. However, the Debian folk decided that the init process 'systemd' was better and moved to it. Raspberry folk went with the trend and moved to it as well.

That was the decision point that caused me to have to rebuild the weather station Pi from scratch. Upstart wouldn't allow the Pi to boot up at all with the new Jessie level OS that you will get when you update your Pi. But, I did all that and now I faced the dreaded systemd configuration. I looked at about a hundred web sites that discussed how to use systemd to handle starting and restarting a process, and quite honestly, was about to give up because everything was too complex and peppered with 'try this' to give me enough confidence to dive in.

At this point I want to mention that following Linux questions on a forum is so predictable. Someone has a problem and asks for help with it. The first response is to ask for more information about how the system is set up, what they saw that wasn't mentioned in the question, and usually some test or other that can't be run because the problem won't let them. Then a response asking them what they are doing. Then a response that says they should be doing it another way because that way is so much better. Then several posts about how it should be done regardless of the original intent. Lastly, the thread ends with no resolution because the discussion has wandered off into never, never land.

I saw that over and over. I finally stumbled across a blog posting (love blog postings) that whittled all the crap down to something amazingly simple compared to what I had been clicking through for hours <link>. Neil cut all the crap out and just showed how to handle a simple case. I didn't have to dredge through long discussions of 'units' or 'forking' or relative merits of daemons; I could just take his example and use it. It worked first try for one of the processes and third try for the other.

Then I configured logrotate to handle the log files I use for debugging and I was done. Finally.

I still have to back up the SD card and store the image on my NAS, and I'll probably back up the other Pi's just to be somewhat safer (obviously you can't actually be 'safe'). I'll also update my github repo for the various things I changed along the way, but that won't assure me of no problems the next time, just make it somewhat easier to recover.

What will really make it easier the next time is what I learned this time.

Monday, May 11, 2015

Yet Another Update to the AcuRite 5N1 Weather Sensor

The previous post on this project is here <link>

Just when you thought I'd finally shut up about the AcuRite weather sensor I decide to post again. I promise, at some point I'll actually be satisfied with this device, but not quite yet. Over the months I've been working with this I've received comments and emails from folk that tried it and discovered things. One reader had an especially interesting situation; he had a neighbor that had a sensor within range. This meant that the code would pick them both up and present data from both sensors. To add insult to injury, when he tried changing his station to channel C, he found out that rtl_433 had a bug that prevented his receiving data on that channel. He must have been really annoyed at that point.

Since wind and temperature matter to placement, this was causing him grief and he set off to fix it. He isolated the sensor ID, and while he was at it, decoded the battery level, channel and found a better way to read the RF that allowed channel C to work.

During his trials he also found that the checksum was allowing bad data into the decoding and would present unusual readings. I've seen this over time as well. Things like a wind gust of 147 mph, a temperature spike of 30 or so degrees for no reason, the usual outlier readings, but with weather, it's hard to tell what an outlier really is.

I totally stole his ideas and some of his code and incorporated into my weather station,; I haven't been running it too long, but it looks like the vast majority of the outlier readings are gone. There may be some turn up, but so far it's been good.

First, inspection of the data showed that the bytes holding the various readings were all even parity. I added code to check the parity and discard the message if it didn't pass. Note that I only check the readings bytes because the first, second and last bytes are sensor, status, and checksum; these don't incorporate parity. In the c code I add the decoded sensor id, battery level, and channel to the JSON string I output, then check for the proper values in the python code that receives it. I did it this way because some folk may want to catch multiple sensors and average or compare them; especially if they can see a neighbors device for free.

I also noticed at my house that I receive a couple of devices from other folk that are not AcuRite devices. These things can cause problems as well, especially since they could easily be using the same checksum algorithm.

All the changes meant that I had to get the very latest version of rtl_433 from the original github repository. This was required because the rtl_433 folk made a new decoding method available that works better with the AcuRite data stream. That decoder wasn't there in January, but it is now. So, I tried again to fork it and failed miserably, and wound up just updating my copy of rtl_433. But, another good thing happened as well. The folk that wrote rtl_sdr, the actual radio software have improved it a lot and it installed first time, and I got to remove that from my copy.

Now, you get rtl_sdr, install it, build it, and then get my rtl_433, install it and build it. That will give you the version I'm using with all the changes to support the newest findings. My python code for the weather station I run is in GitHub as well. The specific stuff I use is:

rtl_sdr <link>
rtl_433 <link>
weather station <link>

The reason I linked to a particular commit in GitHub is so you can have exactly the same code to work with. The two rtl projects are in flux and could change dramatically at any time. You can always get the latest version if you think it will serve you better.

The latest version of rtl_433 not only has the new decoder software, it also allows you to specify the device you expect to receive. That makes it nice for me since I only have the AcuRite device I'm interested in. I changed my start up command to support this feature:

rtl_433 -f 433.915e6 -R9 2>> saveweather.log | saveweather.py  >> saveweather.log 2>&1

The '-R9' specifies the 5N1 device. Notice how I save the stderr output to a file? That helps me understand how many errors I'm getting and could help me fix a problem. Right now I'm also using the -D for debug option as well to get more information. Try '--help' on rtl_433 to see the other options it has.

I owe this latest version to Pcjunky (yes, I know his name, but I ain't gonna post it without his permission). He took my code and beat on it until it worked to his satisfaction. Thank's a lot.

Now, I want to show you the parity routine I ran across. Here's the code:

static int acuriteParity(uint8_t v){
    // returns 1 if parity odd, 0 if parity even
    v ^= v >> 4;
    v &= 0xf;
    return (0x6996 >> v) & 1;
}

This calculates the parity for a byte in around five cpu operations. It's an extremely clever implementation that I leave the explanation as an exercise for the student.

Have fun.


Sunday, April 12, 2015

Getting Back To The Weather Station

I know I've worn out my welcome about the AcuRite 5n1weather station, sorry. This time it's a bit different and I'm really, really open to input and ideas. I decided that the console that came with the weather sensor is too strange and quirky to deal with long term and set up a Raspberry Pi to act as my weather station. I used a radio and directly receive the transmissions from the weatherhead <link> instead of the USB interface I worked on forever <link>

I have a lot of work to go on it, but I'll update it over time as I figure out what I actually want. To date, I set up a little SQLite3 database and record each of the readings there on a periodic basis. Currently, I update each record either as it comes in, or every minute. The reason for the difference is that my barometer and outside temperature sensor is on my XBee network and handled by a different machine, so I only interrogate it on a minute basis.

Yes, the little Pi weather station is a separate machine and I have it talk to the Pi that controls my house to get the two readings.

Actually, let me elaborate on this a bit. Remember way back when I posted about bringing up an HTML interface for the various processes I run using CherryPy <link>? That little effort worked incredibly well. For most processes I have at least two interfaces, one that I can read and one that is formatted in JSON that a machine can read. So, to check on a particular process, I just type the IP address and a port number into the browser and the process responds so I can see whatever I set up. This interaction doesn't care what machine it's on or where the request came from. Using this I have the weather software on one Pi, then get the readings for barometric pressure and outdoor temperature from another one. It's so cool.

I may expand on this idea and have several Pi's running doing different things for me. I could put the XBee network on a machine by itself and have other things talk to it to get the data. Sure it raises the risk of failure because it increases the number of machines that can fail, but It also means I have all the power of a Pi to do a task and not have to worry about things bogging it down. I may move all the upload processes to a single machine so that stuff is separate as well.

But enough of that, I'm here to talk about the weather station. I keep data on a periodic basis for: Rain counter, Outside Temperature on the fence, Outside Temperature at the weatherhead, Barometric Pressure, Wind Speed, Wind Direction, and Humidity. I also keep a daily record, written at midnight that hold the Barometric pressure at that moment, the high and low temperature for the day, and the rain counter at that moment. I create this record once a day, so I can use it as a daily data point on a graph.

From this I can calculate the daily rainfall, and when I have enough data, the weekly, monthly, etc for my location. I have enough data to graph the temperature on a daily basis, and even use the midnight record to go longer term. That stuff, of course, will depend on gathering the data over time. Or, I could download the data from Xively and fill it in for the past if it becomes relevant.

This looks like it will eat up about a megabyte a week in space, and I have roughly 4 Gig available on the Pi, so I have some time to get an NAS online to hold the data long term.

But, as usual, I have no idea what the heck I'm doing. I don't have a clue what I may want next month, or what is the best way to gather the stuff that I don't know about yet. The station is running right now, but it isn't hooked into the web yet so I can't show it off. I'll get to that in a week or so.

I looked into loading the stuff up to Weather Underground, but that didn't excite me very much. I've been fighting cloud providers for a few years now and I'm not impressed too much anymore. With the terabytes of NAS storage available these days and the ease of bringing up a significant little computer, who needs the headaches of specialized protocols and changing terms of service?

So, you folk that have weather stations and such, what am I missing?

Oh, the code isn't on GitHub yet, I'm not sure how I want to arrange it there. I may set up another repository for this instead of adding it to what's already there. I need to think about this a while. But, if anyone wants to take a look, let me know and I'll move more quickly and make it available.

The next post on this project is here <link>.

Saturday, February 14, 2015

Acurite Console Barometer: OK, This is it.


Back in a previous post I tried to decode the barometer reading from the AcuRite console.  I just couldn't get it, but one of the readers, Play Kube, stepped up and nailed it for us. The post is here <link> and his findings are down in the comments.  He emailed them to me and I posted them for him. Then I added code to my stuff and recorded the readings for a few days and this is the correlation to my (very accurate) fence post barometer. I spent quite a bit of time making sure my barometer compared to the weather stations around me and it was dead on.  The decoded AcuRite readings follow mine really, really well.  Take a look:


Isn't that great?  There's a couple of caveats though: The console barometer is NOT temperature or altitude corrected.  That's because we don't have access to the temperature or altitude corrections that the chip manufacturer supplies inside the chip. These values can be read and the math applied to get the reading dead on, but we don't have them to work with.

The way I corrected the readings was to simply adjust the algorithm to compensate.  This means that if I move the console up or down by a significant amount (I move to sea level) or the temperature changes (put it in the sun) the readings will change.  However, in most of our houses the temperature stays pretty stable and it's unlikely we'll be moving soon.  If one of these does happen, just readjust the algorithm and you'll be all set for the next time.

Looking at the chart, the difference in the readings is less than a mbar and tracks exactly.  Here's the code I used to decode the reading:

                    float bar = 6.23 * (R2[23] << 8 | R2[24]) - 20402;
                    weatherData.barometer = bar / 100; // convert to mbar from pascals
                    weatherData.barometer += 81.1; //adjust for altitude

It's a simple linear equation like a line graph from high school algebra.  Take the last two bytes and form a single number from them, then use a slope of 6.23 and an offset of -20402.  This will get you the barometric pressure reading if you were at the same altitude and temperature as the person that figured it out. So, to adjust it to your location, just go look up the barometric pressure reading for your location at the same time and add the difference in.  That's what the 81.1 number above means, I just added it in as the last step. The changes will be in GitHub soon.

Don't think this is too simple a solution.  The manufacturer of the chip put a lot of work into getting the sensor to follow pressure changes, then they put the adjustments for temperature and fixed altitude in the chip so they can be read and applied.  There was a lot of work done to get it to work. The trick was to get the slope correct, then the offset was just the method of getting it to line up vertically.  The altitude offset is simply necessary to adjust for the specific location.

Slick, but remember, change altitude or temperature and you may have to revisit the algorithm and change the values. We owe this discovery to Play Kube for sending me the mail a few days ago.

I still haven't found the battery level from the weather head though.

Wednesday, February 4, 2015

Reading the AcuRite 5n1 Sensor Set, This time with RF

I've managed to read the RF signal coming from the AcuRite weather head.  It wasn't too hard to do since I had some significant help from other folk that have done the same thing.  The problem was only that there wasn't anyplace out there that I could just look at for an answer that was somewhat coherent to someone that wasn't conversant in the latest digital tuner techniques.  It turns out the answer is quite simple to do, once you work your way through the confusion.  I hope I can clear it up for folk because this is the last piece of the AcuRite puzzle that I think is worth pursuing. 

First, get one of these:


This the tuner chip that is used in digital TV that has been hooked to a USB interface and enclosed in a plastic case.  The chip is a RTL2832 and has a huge bandwidth; it can be used to receive AM, FM, SSB, etc all the way up past the 900MHz range that the AcuRite transmits on.  It's a fun device in and of itself and is the subject of blog posts all over the web. You can listen in to commercial flights, the cops parked at the corner watching your garage, even the doorbell transmitter in your neighbors house.  I didn't want it for boring things like this, I wanted to get the rainfall count out of my 5n1 sensor set without using the console.

So, I downloaded a great tool for doing this kind of thing called SDRsharp and installed it on my laptop.  After visiting several web sites that described how to use its features, and listening to the five or six FM stations I can receive out here, I went looking for the AcuRite sensor.  I found it.


That's it, the spike that is showing on the capture.  It took me a little while prowling around the spectrum to find it, but there it is, Oh ... wait ...


Notice in the bottom panel that there are three different signals that are on almost exactly the same frequency?  Yep, I'm picking up three devices up in the 433MHz area and any one of them could be what I'm looking for.  After a bunch of looking, it turns out the first one I found is the one I want, but how to I get the data out of it? Heck, how do I separate the one I want from the others?

This has already been solved for us under Linux by a tool called rtl_433.  But before we talk about that, use SDRsharp to zero in on the frequency you need to use.  There's a little drift between devices both at the radio and at the transmitter, getting the exact frequency reading will help you later.  Write it down somewhere.

OK, I'm getting too technical without explaining what is going on.  These little tuner chips were designed for digital TV and the description (stolen from here <link> is:
What is RTL-SDR?
RTL-SDR is a very cheap software defined radio that uses a DVB-T TV tuner dongle based on the RTL2832U chipset. With the combined efforts of Antti Palosaari, Eric Fry and Osmocom it was found that the signal I/Q data could be accessed directly, which allowed the DVB-T TV tuner to be converted into a wideband software defined radio via a new software driver.
Essentially, this means that a cheap $20 TV tuner USB dongle with the RTL2832U chip can be used as a computer based radio scanner. This sort of scanner capability would have cost hundreds or even thousands just a few years ago. The RTL-SDR is also often referred to as RTL2832U, DVB-T SDR, or the “$20 Software Defined Radio”.
There's an entire web site dedicated to these little devices at http://www.rtl-sdr.com/ and another at http://www.rtlsdr.com that describes many of the uses people have come up with, so I won't go into it much more deeply.

Next, there's a Linux library that supports this and a really cool tool you can run on your PC to play around. The tool is called SDRsharp and has a dedicated website <link>; just go there and select the download link to get it.  The screenshots above are taken from this tool

Now, before I get into describing how to use this on a Raspberry Pi, you should be aware that these little radio receivers pull a lot of power.  When you plug them into the Pi, it will suck so much power that the Pi will reset and have to boot back up.  If your power supply is not up to it, the radio won't work properly.  A powered hub will help isolate the Pi from the high current, but isn't absolutely necessary if you power supply is strong enough and you don't mind it rebooting when you plug the receiver in.

The Linux library is called  rtl_sdr and is at http://sdr.osmocom.org/trac/, but don't download it yet. Don't misunderstand, this is a nice tool, but not exactly what you want to read the AcuRite weather head. What you want is a tool called rtl_433 that was developed specifically to decode the various devices that operate in the same unlicensed frequency range.  Things like temperature sensors, doorbells, full blown weather stations, etc.

There are a lot of blog posts out there on how to install the rtl_433 tool, but none of them worked for me.  I had to actually follow the instructions as written in the readme file that came with it because the authors had recently added the general purpose library for SDR (software defined radio) when they found out it needed to be changed to help their tool work better. When I downloaded the zip file, I got it all.  It took me about three days to figure that out and get it to compile and work.  Then, when I went back later in preparation for this post, they had changed rtl_433 a bunch. I guess someone got a burst of energy and they updated everything in sight. They had gone back to installing rtl_sdr as a separate step and that would make things much harder.

I just created a new one under my login on GitHub.  I really didn't like the idea of things changing every time I wanted to do something and using it for my weather station would depend on two projects out there that can change at any time.  I tried to use the Git fork and other tools and got totally confused with the various methods of backing up in a respository; I guess my very own version isn't a bad solution.

There is a dependency on libusb though.  If you've already installed my USB code that previous posts described, you've got that requirement already.  If you haven't, you'll need to install libusb. Go to my post on this <link> and follow the part about installing libusb and libudev-dev.  This is a USB device (of course) and is very, very similar to the code I created to read the console device.

Now you're ready to work on rtl_433, so just go to github and grab it <link> by clicking on the 'Download ZIP' button on the lower right.  To build it though, they use a tool called 'cmake' that isn't on the Pi by default.  Fortunately, it can be installed easily

sudo apt-get install cmake

Now you can (finally) just follow the instructions in the readme that comes with the library to build it.  Don't do like I did and think I knew everything and install the rtl_sdr library and try to get it to link up properly with rtl_433; that is a path to almost terminal frustration.  Just build rtl_433 from my copy. You'll probably want to rename it once you get it unzipped to something easier to type.  Here's the commands I used to build mine:
cd Desert-Home-rtl_433-master
mkdir build
cd build
cmake ../
make
Now, you are almost ready to actually read the RF from the weatherstation, but (another one of those) the decoder included with rtl_433 is wrong. I don't know if the author of that particular piece had the same kind of problems I did originally, but he made some of the same mistakes I did.  So, I put together my own version that decodes my weatherhead and corresponds with the console. These changes are part of the zip file you downloaded; you don't have to do anything special.

The great thing about doing it this way is the weatherhead provides a checksum on the data.  That means no more undetected short packets or weird data because something in the AcuRite console is messed up. It also means you only get the 5 sensors included in the weatherhead: temperature, humidity, wind speed, wind direction, and rainfall count.  But, since I installed my own barometer <link>, and plan on building anything else I may need, that's good enough for me.

Just for completion's sake, here is sample output from my weatherhead:

pi@deserthome2:~/src/Desert-Home-rtl_433-master/build$ cd src
pi@deserthome2:~/src/Desert-Home-rtl_433-master/build/src$ rtl_433
Registering protocol[01] Rubicson Temperature Sensor
Registering protocol[02] Prologue Temperature Sensor
Registering protocol[03] Silvercrest Remote Control
Registering protocol[04] ELV EM 1000
Registering protocol[05] ELV WS 2000
Registering protocol[06] Waveman Switch Transmitter
Registering protocol[07] Steffen Switch Transmitter
Registering protocol[08] Acurite 5n1 Weather Station
Found 1 device(s):
  0:  Generic, RTL2832U, SN: 77771111153705700

Using device 0: Generic RTL2832U (e.g. hama nano)
Found Rafael Micro R820T tuner
Exact sample rate is: 250000.000414 Hz
Sample rate set to 250000.
Sample rate decimation set to 0. 250000->250000
Bit detection level set to 10000.
Tuner gain set to Auto.
Reading samples in async mode...
Tuned to 433920000 Hz.
Detected Acurite 5n1 sensor
wind speed: 2.0 mph, temp: 54.5F, humidity: 67%
Detected Acurite 5n1 sensor
wind speed: 1.5 mph, wind direction: NE, rain counter: 344,
Detected Acurite 5n1 sensor
wind speed: 1.5 mph, temp: 54.5F, humidity: 67%
Detected Acurite 5n1 sensor
wind speed: 1.5 mph, wind direction: NE, rain counter: 344,

Notice how the build process put the files in the directory rtl_433/build/src/ ?  I'm not responsible for that ... that was the way I got it.  Just move them wherever you want them to reside at run time.

Remember way above I told you to write down the frequency you found the weather head transmitting on?  This is where you may have to use it.  If it drifted far enough, you won't get a good signal from the transmitter up on the roof or out in the yard.  You can simply specify the frequency you want to listen to with the '-f' parameter like this:

rtl_433 -f 433.915e6

This means to set the receiver at 433.915 MHz.  I actually use this number for my weather head.

There's still some stuff to be done from my perspective, I want to put some code in to present the data as a JSON string so it can be used exactly like I did in the other version.  I also want to wait until I have both types of messages in hand before I output anything.  I'll be doing that over time and moving to exclusively use the RF from the weather head.  That will eliminate all the little irritations that the console entails. So, if you grab the code at some point down the line, there will be changes that reflect my specific use; if you want this particular version, the commit # in Github is 5c72e32 and you can get to it by clicking on 'Commits', then finding the number in the list and clicking on the browse next to it that looks like this <> . That will get you back through any changes to the level it was when I wrote this.

This will also make the Raspberry Pi a possible replacement for their expensive bridge device that is limited in what it can do and completely closed to us changing it.

But what will I do with the console?  It's actually an attractive device and I think I'll put it in the kitchen somewhere.  It will give me a nice conversation piece in the most often used room.

Have fun.

Edit Feb 13, 2015:  One of the readers, John, had a problem with attaching to the SDR device.  He chased it down to some code needing to be added in the USB open code.  He emailed it to me and I tried out his fix and it worked fine.  The problem relates back to removing the default hardware driver when starting up like I mentioned way back in a previous post <link>.  I tested it to be sure it would work for me as well and it was great.  I've already put the change into the github repository as commit number 86c70cf .  See, it does pay off to read all the posts after all. Feel free to grab it

Monday, January 5, 2015

AcuRite Weather Station, Raspberry Pi, Fun Stuff, Part 8

Part 7 of this series is here <link>

I mentioned way back in part 1 that I visited a site and discussed a linux version of the AcuRite weather station interface with a guy on Valley Information Systems discussion board <link>.  The person was the professed author of the VIS sofware that is provided by AcuRite and he has a greatly expanded and pretty cotton pickin' slick version that can be subscribed to as well.  Michael Walsh is his name and he's pretty sharp, but very, very snarky.

You've all seen this, especially from some of the dweebs on the various forums that belittle every post that is put up on the site.  I'd show you the post so you could get a feel for what I'm talking about, but he removed it.  Yep, when I went back and posted about my success with reading the USB port on linux and starting to decode the various items, I got a subscription notice that my post had been moved to the archives.  Which generally means it can still be found by a search on the forum, but it doesn't appear in the lists of posts.

Today, I got another notice and when I clicked on it:

The topic or board you are looking for appears to be either missing or off limits to you.

Heh, it looks like it's off the board entirely.  When I looked for 'linux' on the board, there's been a couple of folk looking around for a linux version recently; I may have become competition for him.

Keep in mind, I don't know why he moved the posts, or why he eventually made them disappear, it could just be weird traffic or some accident.  But it is cool that the posts of a successful, FREE, version that reads the AcuRite console's USB output on a little Raspberry Pi that doesn't tie up your home PC have disappeared.

But, once it's on the internet, it gets read, copied, backed up, mailed about, etc.  Especially since a few folk have helped me on this project by finding bugs and running it a lot in their own home.  I just typed "acurite raspberry pi" into Goodle and guess what site came up as the first entry?  hee hee.

So, you weather station folk out there, folk that got one of these for Christmas, people that succumbed to the impulse of the cool packaging at Costco (I'm in that group) spread the word.  If enough folk get involved, this could lead to a great weather station package.  I just don't have the hardware, facilities, time, or diversity in my network to do a really good job.

But wouldn't it be cool

Monday, December 29, 2014

AcuRite Weather Station, Raspberry Pi, Refining the readings Part 7

The previous part of this project is here <link>

Yes, another post on this device.  Even with this one, I'm not done; there will be others since I want to sample the RF from the sensor at some point and also bring up a separate little computer for just the weather station.  But I was extremely lucky and one of my readers double checked my work and found a couple of mistakes.

Thanks go to Heath Paddock <link>; he double checked the wind speed and direction and found mistakes, then gave me code to correct my mistakes.  Thank you Heath.  Due to a couple of factors completely within my control I misread the wind direction conversion.  He noticed it when he was testing the device and dropped me a line.  Then he double checked the wind speed and noticed a mistake there as well.

I was getting the bits correctly, but the conversion was off; let's look at the wind direction first.  The correct table for this sensor to convert from a number to direction looks like this:

// Array to translate the integer direction provided to text
char *Direction[] = {
    "NW",  // 0
    "WSW", // 1
    "WNW", // 2
    "W",   // 3
    "NNW", // 4
    "SW",  // 5
    "N",   // 6
    "SSW", // 7
    "ENE", // 8
    "SE",  // 9
    "E",   // 10
    "ESE", // 11
    "NE",  // 12
    "SSE", // 13
    "NNE", // 14
    "S"  };

Every single value is different; when I mess up, I do it in style.  When he sent me the code, I climbed up on the roof and brought down the station to check and see what is happening.  Yep, North was a six coming out of the USB port in report R1.  I went through each of the other directions and they matched his numbers exactly, then I looked at my console and it was wrong.  That just didn't make sense at all, so like all good computer users, I reset the darn thing.  It was still wrong.  I finally unplugged the console from the USB port on the Pi, pulled the batteries out of the console, and just to be complete, pulled the batteries out of the weather head.  This is a sure sign of desperation, when you do something that you know won't help just because you can't think of anything else.

But that did it.  A value of six (the pointer on the weather station was taped in place by now) corresponded to a N reading on the console.  I have no explanation for what was going on, but it works fine now.  No, I'm not going to try and recreate the situation.  I'm quite happy with the way it works and will double check the readings each time I have to change batteries in either the console or the weather head.

At any rate, the wind direction seems to be working really well now.  Next is the wind speed; Heath noticed an odd reading and asked me about it.  I had seen the reading the day before I left for Christmas, so I knew what he was talking about, but I didn't have time to look into it.  He took the time to examine the data he was receiving and found the solution.  The wind speed sensor is granular to .5 MPH; yes MPH, not KPH, and is reported back doubled.  The console does the usual 8 bit math and puts up a reading that is slightly off and loses the .5 granularity.  It also does some degree of smoothing and only reports the speed at intervals so you have to be somewhat patient to see what's going on.  Here's the code I'm currently using:

int getWindSpeed(char *data){
    int leftSide = (data[3] & 0x1f) << 3;
    int rightSide = (data[4] & 0x70) >> 4;
    float speed = (leftSide | rightSide) / 2.0;
    return(speed);  
}

Notice that I'm using a float to preserve the .5 granularity?  I did that to get the most out of my sensor and because it looks cool on my web display.  It won't match the console exactly, but that isn't really my goal.

There will probably be something else some clever person turns up that I missed or interpreted wrong, but that's part and parcel of hacking into a device with little or no information up front.  The point is that multiple people have participated in this project over many miles of separation, and we all get to use it.  I'll have the code updated in Github in an hour or so as well. <GitHub link>

Now I have to climb back up on the roof and put it in place.  There's a storm coming and I don't want to miss it.

The next part to this series is here <link>

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.

The next part to this project is here <link>.
Also, I've made progress on a barometric sensor that actually works <link>
With help from one of my readers the console barometer has been conquered <link>

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>

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