Quantcast
Channel: Desert Home
Viewing all articles
Browse latest Browse all 218

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

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

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.


Viewing all articles
Browse latest Browse all 218

Trending Articles