Saturday, January 31, 2015

Let's Finish Off the Barometer Project.

In the last entry <link> I posted the code for my barometer; I took a simple program of a few lines and turned it into a monster that does everything imaginable.  Now, I should actually set it up to work and put it in service.  Yes, my XBee shield came in and worked fine first try, so I mounted the little breakout board on it and added four wires to connect the barometer chip to the Arduino pins and tested it.


Made a nice little package when I got it all put together, so I dismantled the Stephenson Screen on my fence and tie wrapped it inside.


I had the old temperature sensor in there for years with no problems, so I don't think I need to get any fancier than that.

One thing I did in the code that I didn't mention in the last post was how to correct the reading to adjust for sea level comparisons.  See, to make barometer reading useful around the world, weather folk adjust them for the altitude so that there is a common base for comparison.  There's some really good explanations for why and how to do this out there, so if you don't already understand this, do a quick search.

From our perspective, this is incredibly easy; just call a different routine to read the data:

float pressure = bmp.readSealevelPressure(altitude)/100.0;

This statement will get the pressure adjusted for 'altitude' and the division by 100 will convert it from Pascals to millibars.  If you want inches of mercury, just look up the conversion factor.  The Adafruit library has already got the code for the conversion, all you have to do is look up your altitude in Google Maps, or read it off a GPS at the location where you put the barometer.

I've already adjusted my house monitor code to use this device and if you look at my house display, the outside temp and barometric pressure are taken from the new set up. I was lucky enough to catch a small storm so I could compare it with the other stations in my area, and it was right on.  It's really great when a sensor does exactly what the datasheet says it will.

I really like this sensor; the temperature measurement is very stable as is the barometer.  It's a snap to read and only takes two signal wires for all of it.  Since there's room on the device for more sensors, I may think about luminance or something in the future.

All this playing with the AcuRite weatherstation has been fun, and now I'm going to look at conquering the RF signal it sends.  What I'm going for now is far, far away from what I started off to do, but isn't that how it goes?  I want to grab the RF directly and use that data along with any other sensors I need to add and bring up a separate Raspberry Pi to handle weather presentation. Remember, the code is already in GitHub for you to grab <link>.

Funny how these projects tend to take on a life of their own.

Wednesday, January 28, 2015

My Barometer, and Tips and Tricks With XBee Device Code.

In the last post <link> I put together a basic barometer to substitute for the silly one on the AcuRite console that came with my 5n1 weatherhead.  My XBee shield still hasn't come in, but I got a wild hair and decided to go ahead and put together the code while I waited.  As I was stealing pieces from various other projects I've done and combining it to implement this latest one, I realized there was a significant piece of code I haven't shared yet.

Early on I got tired of having my XBee set up wrong because I forgot something when I configured it and created a little piece of code to check the configuration to some degree at boot time on the Arduino.  By sending AT commands to the XBee, you can check things like software version, operating mode, etc.  Then if something was messed up, you can correct it easily without a long debugging session to discover some minor setting error.  I also use this to get the name of the device, and the address that it normally sends to.  Using this piece of code makes the entire device more configurable.  For example, you need to change to broadcast, instead of a specific address, to monitor the packets, just use XCTU to remotely change the DL, DH attributes and reset the Arduino so it reads the new address.  If you decide to change the device name, remotely change the NI attribute, and again, reboot the arduino.  I've been using this in one form or another now for a while to help work the kinks out of the latest device I'm working with.

Yes, you can change the configuration of the XBee that you stuck in the corner of the attic remotely,  This has been a capability of XCTU for a long time, and the latest version of XCTU makes it even easier.  To remotely reset an Arduino, I simply decode a command I send and then call location zero.

These two tiny tricks makes minor changes to the network really easy and can be done from my recliner.

The code that follows (below somewhere) is a simple thing with complete XBee interaction,  I have examples of sending formatted strings containing float data through the XBee, parsing a long unsigned integer out of an XBee message and using it, simulating the data portion of an XBee message for debugging, setting up a timer to send status periodically, reading some of the configuration out of the XBee to be sure it's OK before continuing the work.  That kind of thing.

Since the barometer code is so easy to understand, I thought that would be a perfect device to put all this stuff in and use as an illustration.  Go take a look at the barometer <link> then come back here and I'll step through some of the things that you can use in your own code to make the XBee a valuable device in do it yourself home automation.

First, just to get it out of the way, resetting the Arduino from software.  If you haven't hit it yet, you will, that time when you know a reset will cure a malfunction on the Arduino, but its up high, out in the yard, in the dark on the side of the house; somewhere hard or inconvenient to get to.  Wouldn't it be nice to just send a command to it and have it reset itself?  A software reset is easy to do.  If you need a hard reset (similar to pulling the plug) this won't do it; but those are not often needed.  Just put this line somewhere at the top of the code:

void(* resetFunc) (void) = 0; //declare reset function @ address 0

and when you need it, call it like this:

resetFunc();  //This will reset the board and start all over.

Yep, that's all there is to it.  I use this a lot for overcoming memory leaks in libraries that haven't been tested long enough, memory loss from using Streams in c++, that kind of problem.  It also helps overcome my own bugs from time to time.  As an example, in the code below I use it twice.  I have one software reset that is on a timer to reset the board every day around midnight.  This is to recover memory lost to whatever code does a malloc() and then doesn't remember to do a free() to release the memory back.  This is a very common problem (called memory leak) and has plagued tiny computers as long as they've existed.  When you want to run an Arduino 24/7, you have to code defensively.  The other instance is when I send a reset command over the XBee network to cause the board to reset.  I use this to test things like power failures, how long it will take to settle down, and to see if a reset will work before I get the ladder out to take a physical look at it.

Since I mentioned remotely resetting the board, I'll describe how I do that, but I need to explain how I use the IDE itself first.  Most of you already know that the Arduino IDE supports 'tabs', but the new folk haven't caught up to that yet.  Tabs are actually separate source files that the IDE stores in the same directory as the sketch.  These are extremely nice for organizing code so you don't have to scroll through a couple of hundred lines of code to get to something you want to work on.


What you're looking at above is the Arduino IDE with three tabs.  One is for the bulk of the barometer code, the next is for some utility routines I use a lot, and the one selected is the XBee code I use on a lot of my devices.  I opened the window that controls tabs so the new folk can get a look at the tool provided for working with tabs.  Remember, each tab is actually a separate file in the sketch directory.  When you compile something, the files are all concatenated together and passed to the compiler, this is just a convenience to help you organize your project.  There's a couple of tutorials out there that mention tabs, and I've heard of a book or two that do, but most of the instructions are great at telling you how to install it, but not how to use it effectively.  So, don't be afraid to poke around and experiment.

No, I'm not going to write a book about it.  If I did, they'd change it and I'd have to have a second revision.  I've got projects to work on instead.

Anyway, The picture shows how I decode the command and reset the board.  Over time I've worked out my own particular method of sending and receiving packets that works well for me.  Everything is done in a string; I don't try to send integers, floats or compact the data.  For home automation, interactions are measured in a minimum of seconds and plain text traveling between the machines is perfectly adequate.  It also makes monitoring what's going on much easier since I don't have to decode anything to read it, it's already text.  So my command to reset the barometer is:

Barometer,Reset

I can type that right into an XCTU session from my recliner and the barometer out on the fencepost (where it will eventually reside) will reset itself.  Yes, I have to construct a packet in API mode 2 format, but XCTU has a packet generator that works pretty well to help with that.  

Now, let's talk about AT commands to the XBee itself.  When you look at the various tutorials on the web, they love to type AT into the XBee console and look for the OK that comes back.  Then they teach you how to set up the XBee using these commands.  This is good, but not very useful if you're going to actually do a project that will run all the time and have to survive a lot of the normal problems such as RF noise, distance, flaky devices, etc.  For that you have to go to API mode and send fully constructed packets between machines.  I have already covered the various pitfalls and have instructions on how to set this up on my XBee page, so I won't go over it again here, go here <link> for that when you need it.  What I want to show you is how to send an AT command, get something from the XBee and then use it.  Doing this you can double check the configuration, get the device name, etc.  It's a really good feature to understand.  As an example, here are the first few lines executed on my barometer:


I know, it's a dumb screenshot.  I did this on purpose, to illustrate again how tabs can work for you.  I have selected the barometer tab and scrolled down a bit to the setup() routine.  See how you can flip between tabs to do things instead of scrolling though tons of lines of code and losing your train of thought?  I like this feature.

Anyway, I set up the XBee code, then call getDeviceParameters().  This routine sends a series of AT commands to gather various items and then returns.  Then I display the parameters so I can tell if I have the XBee set up the way I want it.  I actually store the string 'Barometer' in the NI command; this is the name of the device and I use it to check incoming packets to see if they're for this device and include it in the out going status packets I send.  This way I can change the name of the device by simply changing the parameter on the XBee.  Here's the code that does this:

void getDeviceParameters(){
  uint32_t addrl = 0;
  uint32_t addrh = 0;
  uint32_t daddrl = 0;
  uint32_t daddrh = 0;
  
  if(sendAtCommand((uint8_t *)"VR")){
    if (atResponse.getValueLength() != 2)
      Serial.println(F("Wrong length in VR response"));
    deviceFirmware = atResponse.getValue()[0] << 8;
    deviceFirmware += atResponse.getValue()[1];
  }
  if(sendAtCommand((uint8_t *)"AP")){
    if (atResponse.getValue()[0] != 0x02)
      Serial.println(F("Double Check the XBee AP setting, should be 2"));
  }
  if(sendAtCommand((uint8_t *)"AO")){
    if (atResponse.getValue()[0] != 0)
      Serial.println(F("Double Check the XBee A0 setting, should be 0"));
  }
  
  if(sendAtCommand((uint8_t *)"NI")){
    memset(deviceName, 0, sizeof(deviceName));
    for (int i = 0; i < atResponse.getValueLength(); i++) {
      deviceName[i] = (atResponse.getValue()[i]);
    }
    if (atResponse.getValueLength() == 0){
      Serial.println(F("Couldn't find a device name"));
    }
  }
  if(sendAtCommand((uint8_t *)"SH")){
    for (int i = 0; i < atResponse.getValueLength(); i++) {
      addrh = (addrh << 8) + atResponse.getValue()[i];
    }
  }
  if(sendAtCommand((uint8_t *)"SL")){
    for (int i = 0; i < atResponse.getValueLength(); i++) {
      addrl = (addrl << 8) + atResponse.getValue()[i];
    }
  }
  ThisDevice=XBeeAddress64(addrh,addrl);
  if(sendAtCommand((uint8_t *)"DH")){
    for (int i = 0; i < atResponse.getValueLength(); i++) {
      daddrh = (daddrh << 8) + atResponse.getValue()[i];
    }
  }
  if(sendAtCommand((uint8_t *)"DL")){
    for (int i = 0; i < atResponse.getValueLength(); i++) {
      daddrl = (daddrl << 8) + atResponse.getValue()[i];
    }
  }
  Controller=XBeeAddress64(daddrh,daddrl);
}

uint8_t frameID = 12;

boolean sendAtCommand(uint8_t *command) {
  while(1){
    frameID++;
    atRequest.setFrameId(frameID);
    atRequest.setCommand(command);
    // send the command
    xbee.send(atRequest);
    //Serial.println("sent command");
    // now wait a half second for the status response
    if (xbee.readPacket(500)) {
      // got a response!
  
      // should be an AT command response
      if (xbee.getResponse().getApiId() == AT_COMMAND_RESPONSE) {
        xbee.getResponse().getAtCommandResponse(atResponse);
  
        if (atResponse.isOk()) {
          //Serial.println("response OK");
          if (atResponse.getFrameId() == frameID){
            //Serial.print("Frame ID matched: ");
            //Serial.println(atResponse.getFrameId());
            return(true);
          }
          else {
            Serial.println("Frame Id did not match");
          }
        }
        else {
          Serial.print(F("Command returned error code: "));
          Serial.println(atResponse.getStatus(), HEX);
        }
      }
      else {
        Serial.print(F("Expected AT response but got "));
        Serial.println(xbee.getResponse().getApiId(), HEX);
      }
    } 
    else {
      // at command failed
      if (xbee.getResponse().isError()) {
        Serial.print(F("Error reading packet.  Error code: "));  
        Serial.println(xbee.getResponse().getErrorCode());
      } 
      else {
        Serial.println(F("No response from radio"));
      }
    }
  }
}

It's not perfect code, and it doesn't serve the 'general case', but it is easy to understand and does the job.  Notice in the routine sendAtCommand() that I hang up in an infinite loop waiting for a response to each AT comand I send?  Yes, that's on purpose.

On a network like mine where there are a number of XBees sending data at essentially random intervals, traffic can be mixed in with the responses to the AT commands.  I send a request for the software version at the same time as a time update is received from my house clock.  The XBee will send the packet from the clock first and I could miss the response to the AT command.  Also, the receive for the packets is interrupt driven and characters can be missed by simultaneous sends and receives.  To overcome these kinds of problems, I wait for the response about a half second, if it doesn't come back, I try it again.  This guarantees that I will accumulate the items I need during initialization regardless of traffic conditions, it just may take a couple of tries to do it.  It's actually kind of fun to watch it during high traffic conditions; it tries each command as many times as necessary to gather the data and then returns to let setup() print them out.  Here's what the output looks like when I run it:

Barometer Version 1 Init...
This is device Barometer
Device Firmware version 2370
This Device Address is 0x0013A200 0x4089D915
Default Destination Device Address is 0x0013A200 0x406F7F8C
Setup Complete
Mem = 276
Barometer: temperature: 77.5, uncorrected pressure: 937.8
See how the name of the device, long address, version, destination long address can be gathered directly from the XBee?  This is nice to guarantee that you set things up correctly when you try your code out.

Now, let's look at sending the actual data that I gathered from the barometer chip.  I've learned to like JSON encoding as a method of passing data and am converting all my devices to send data using this format.  I haven't gotten very far on that project, but the barometer is new, so I use it here.  I use this line to construct the status text that is sent:

void sendStatusXbee(){
  float temperature = bmp.readTemperature()* 9/5 +32;
  float pressure = bmp.readSealevelPressure(altitude)/100.0;
  sprintf(Dbuf, "{\"%s\":{\"temperature\":\"%s\",\"pressure\":\"%s\",\"utime\":\"%ld\"}}\n", 
            deviceName, // originally read from the XBee
            dtostrf(temperature, 1, 1, t), // This is a text conversion of a float
            dtostrf(pressure, 1, 1, p),  // same as above
            now());  // current time in seconds
  Serial.print(Dbuf); // notice this is only for the serial port
  sendXbee(Dbuf);     // out to the XBee
  Serial.println("Pressure message sent");
}

I use sprintf() because the documentation is all over the web and there are tons of examples out there for people to follow.  The problem is that the Arduino sprintf() doesn't support floating point, but does have a handy routine dtostrf() that will turn a float variable into a nice little string that can be used.  Pay particular attention to the format string I used; you don't have to do it this way.  You can assemble any string you want to and send it instead.  Like I said though, JSON is already documented on the web and I don't have to invent anything this way.

Now, I want to show you how I control where the message is sent.  I read the default address from the XBee DH and DL parameters during initialization and put it into the variable 'Destination', now I get to use the variable:

void sendXbee(const char* command){
  ZBTxRequest zbtx = ZBTxRequest(Destination, (uint8_t *)command, strlen(command));
  xbee.send(zbtx);
}

This will take the string I just constructed and put it into an XBee packet, set the address I mentioned and send it.  To change the address such that the message is broadcast to all devices, I decode a command and just change the value of Destination:

    // The ability to broadcast is sometimes used to see what it going on
    // using an XBee plugged into a laptop to monitor the data.
    else if (strcmp(nxtToken, "SendBroadcast") == 0){
      Serial.println(F("Switching Address to Broadcast"));
      Destination = Broadcast;     // set XBee destination address to Broadcast for now
    }
    else if (strcmp(nxtToken, "SendController") == 0){
      Serial.println(F("Switching Address to Controller"));
      Destination = Controller;     // set XBee destination address to Broadcast for now
    }

This is just like the Reset command above; I send

Barometer,SendBroadcast

and it switches the Destination variable to the broadcast address and from then on every XBee in the network will see the message.  To put it back to normal:

Barometer,SendController

And, this brings up another feature I like to have on my remote devices, a nice way to test the commands without having to drag out an XBee breakout board every time I want to add or try something.  Wouldn't it be nice to be able to simulate the receipt of a command through the IDE serial display?  I worked up this code to allow that very thing:

  // This allows emulation of an incoming XBee command from the serial console
  // It's not a command interpreter, it's limited to the same command string as
  // the XBee.  Can be used for testing
  if(Serial.available()){  // 
    char c = Serial.read();
    if(requestIdx < sizeof(requestBuffer)){
      requestBuffer[requestIdx] = c;
      requestIdx++;
    }
    else{  //the buffer filled up with garbage
      memset(requestBuffer,0,sizeof(requestBuffer));  //start off empty
      requestIdx = 0; // and at the beginning
      c = 0; // clear away the just-read character
    }
    if (c == '\r'){
      requestBuffer[requestIdx] = '\0';
//      Serial.println(requestBuffer);
      // now do something with the request string
      processXbee(requestBuffer,strlen(requestBuffer));
      //start collecting the next request string
      memset(requestBuffer,0,sizeof(requestBuffer));  //start off empty
      requestIdx = 0; // and at the beginning
    }
  }

This fragment will allow me to type a command that would normally come in over the XBee right into the IDE serial monitor and send it to the code.  It does this by just gathering the characters and calling the same routine I use to decode XBee commands.  This cuts short the time it takes to add another command to the device.

Now, normally, I would post the code of the entire module, but I bet you've had enough code samples in windows for one session.  Instead, I finally got the Arduino code I'm using for the various things into GitHub.  This entire module is in the 'Barometer' directory so you can grab it and modify away.  It's been running for a few days and hasn't blown up yet, but I'll certainly be adding to it over time.

If you go looking in the other directories don't expect all of these features to be in each thing.  Like I said, I developed these tricks over time and only recently started to go back and update the older devices.  Heck, I haven't changed the thermostats at all in months.  One of the modules hasn't even been compiled under an IDE version in a couple of years.  I'll work through the devices one at a time as I have the chance, and probably come up with another trick that I'll want to fit into the others.  It never ends.

Here's the link to GitHub  <link>. Have fun.

Tuesday, January 20, 2015

Barometric Pressure, Wow, that was easy

My barometric pressure sensor came in today.  I posted about ordering it a short while ago because the shipping costs at Adafruit (my preference) were too cotton-pickin' high, and man, I was totally surprised when it came in today.  The other thing that surprised me was how small this thing is.

Sure, the pictures compare it to a coin and that should give a person a good idea how small it is, but when you unwrap it and have to actually look around to find it inside the shipping envelope, it comes home full force.  This thing, breakout board and all, is tiny.


Notice how small it is compared to the Arduino?  That means I have some choices in exactly how I implement the final installation.  Another thing that surprised me was how incredibly easy it was to implement the code to read both temperature and pressure from the device.  Yes, I was standing on the shoulders of giants that have trod before me, but that's what open source is all about.  First, I grabbed the Adafruit Unified Sensor Driver and tried it out.  I had compile errors in the examples supplied.  Frankly, that was annoying since I've never encountered that before at Adafruit, so I grabbed their older, deprecated, version 1 library specifically for the BMP-085 and tried it.

Worked first time.

So, fine, I'll just use the older library because it was getting late and I really don't want to hunt down some big company's compile error.  A deprecated library that works is perfectly OK with me.  Of course I made a few changes to the example because it gives the pressure in Pascals and the temperature in Celcius, I wanted mBar and Fahrenheit.  I also want the string I use to be a JSON string when I send it over an XBee to my Raspberry Pi.

So, about twenty or so minutes later I had this script:

#include "Wire.h"
#include "Adafruit_BMP085.h"
 
Adafruit_BMP085 bmp;
char buff1[50];
 
void setup() {
  Serial.begin(9600);
  bmp.begin();  
}
 
void loop() {
    char t[10], p[10];
    
    float temperature = bmp.readTemperature()* 9/5 +32;
    float pressure = bmp.readPressure()/100.0;
    sprintf(buff1, "{barometer:{temperature:%s,pressure:%s}}\n", 
            dtostrf(temperature, 3, 1, t),dtostrf(pressure, 4, 1, p));
    Serial.print(buff1);
    delay(500);
}

Yes, that's all there is to implementing one of these devices.  How much easier can it possibly get? Here's what the output looks like:

{barometer:{temperature:74.5,pressure:930.4}}
{barometer:{temperature:74.5,pressure:930.4}}
{barometer:{temperature:74.5,pressure:930.4}}
{barometer:{temperature:74.5,pressure:930.3}}
{barometer:{temperature:74.5,pressure:930.4}}
{barometer:{temperature:74.5,pressure:930.3}}
{barometer:{temperature:74.5,pressure:930.3}}
{barometer:{temperature:74.5,pressure:930.4}}
{barometer:{temperature:74.5,pressure:930.3}}
{barometer:{temperature:74.5,pressure:930.3}}
{barometer:{temperature:74.5,pressure:930.3}}
{barometer:{temperature:74.5,pressure:930.3}}
{barometer:{temperature:74.5,pressure:930.4}}

I still have a bunch of work to do with this device, I have to hook it to an XBee, and install it into my Stephenson Screen out on the fence.  That will require waiting for the XBee shield I ordered for this project to come in.  I intend to put an Arduino, the shield, the XBee and this new BMP-085 all out on the fence in the weather and see what happens over time.  So expect at least one more post on this part of the project.

These days a basic Arduino clone can be bought for less than $10, an XBee shield for about the same and the BMP-085 for a few more dollars.  The accuracy on these things is quite good, and if necessary over time, calibration is a simple code change.  Nice project for a beginning weather buff that wants to go digital.

Have fun.

Thursday, January 15, 2015

I Want to Whine About Shipping Costs.

I don't know about the rest of you, but sometimes one of my projects costs more in shipping fees than components.  Getting a couple of chips and resistors across the country can make a project cost more than the fun of doing it is worth.  That's why more and more of my projects are based on cheap Chinese products shopped from one of the Chinese suppliers or eBay.

Sure, I'd much rather buy locally, if there was a locally available to buy from, but the industry has moved completely away from that model...  There are no electronic shops within any reasonable driving distance of my place.  The cost of getting something from somewhere else has trained me to look at the shipping cost very carefully, and my big mouth has just gotten a thread closed at one of my favorite supplier's forum.

The story: I want to follow up on my threat of getting a barometric sensor and setting it up to read temperature and air pressure inside a Stevenson screen I have on a fence post in the yard <link>, so I went to Adafruit and found what I wanted.  It was a:

BMP180 Barometric Pressure/Temperature/Altitude Sensor- 5V ready PID: 1603  $9.95

This is a great little breakout board (from the description) and would be a lot of fun to work up in the project.  However, as I was checking out, the shipping price came up:

UPS Ground: $13.23

There were other options, all more expensive than this.  What?  That's almost 150% of the cost of the item.  So, I went to their forum and looked to see if anyone else complained about this.  Of course someone did, so I did too.  I never want to be outdone in the complaint department.  When it became clear to the folk running the forum that I wasn't going to be shuffled aside by sycophants citing how hard and expensive it was to ship items, the main purpose of their business, they closed the thread. Maybe I should start a forum (not likely), because I could get the last word every time.  All you have to do is type something in, then close the thread so no one can respond.

No, I wasn't abusive, I think I was polite and reasonable, but in your leisure (those of you that have any left), take a look for yourselves <link>.

Yes, yes, I know all about how much it costs to staff and support a shipping department.  I understand completely the inconvenience of getting stuff to the post office and organizing everything.  But, isn't that what got this business going?  The huge problem of shipping is why the distributor model came to be. One company skilled in making a product sells it to another that is skilled in warehousing and shipping.  I took that class in college too.

And, they just announced a solution that is actually pretty good; they have joined with DigiKey.  So, let's see what that would do to solve my particular problem: Darn, the pressure sensor isn't listed as an Adafruit item.  Ok, I'll just pretend it was, I'll pick some other item that weighs roughly the same and compare it between Adafruit and DigiKey; I chose the:

ADS1015 12-Bit ADC - 4 Channel with Programmable Gain Amplifier PID: 1083  $9.95

and the shipping was the same at $13.23.  At DigiKey, the shipping for it was $3,23, exactly $10.00 less.

Yes, I know that ordering more stuff would spread the shipping cost across multiple items making it easier to justify, but I don't want to order a bunch of items just to stockpile them for later, they get lost or chewed on by the squirrels that sometimes get into my garage.

But just to fill in the blanks, suppose I bought two of them; would that make it better?  Nope, two boards cost exactly the same for shipping at both Adafruit and DigiKey: $13.23 and $3.23. Too bad I don't need two barometric pressure sensors.

I was going to go back and compliment them on the deal they made with DigiKey, but they had closed the thread already, so I couldn't.  But, not to be forestalled, I commented instead on the cool looking blog post they did on DigiKey (just today mind you, after I complained publicly), but the comment hasn't appeared yet.  It's a moderated blog, so it may take a while for the comment to make it past the censor (free speech doesn't apply to someone else's blog), or it may not show up at all.  I'll check back later to see if they let the comment through <link>, or maybe I'll forget about it.

Don't misunderstand, I absolutely love Adafruit.  They constantly surprise me at their offerings and the support they give to their customers.  Great company, and I'll continue to deal with them, but order from one of their distributors because I just don't want to throw money down the drain on shipping costs. Note that SparkFun still uses USPS shipping and a similar purchase would be comparable to DigiKey shipping.  And, of course, sometimes the additional shipping cost is worth it; times when you want it before the weekend, something broke and you need it fixed right now, or the ton of other times when waiting a week or more is inconvenient. That's not the case most of the time.

The barometric sensor is on its way from China, but that ADC listed above looks like it would work for another project I have in mind.  And, I have an active DigiKey account.

Saturday, January 10, 2015

Wemo Light Switches, a Completely New Approach

I have several posts on implementing the Wemo switches into my house <link, link, link> and of course a couple of other modifications to make things a little easier; lastly, where I finally found out what was causing them to just go away from time to time <link>.  Even with all this attention, the things still are an aggravation.  But not just for me; one of my readers, Glenn Tracy, has several of the switches, and one Wemo Insight plug that have been driving him nuts.  Glenn made the serious mistake of mentioning arp, so I enlisted him to try the various ideas out as I coded them (he probably regrets that now), and I started exploring possibilities.

The devices pretty much stay on line since I found a cause of the intermittent failure, but then they get hard to find because they don't respond well to Upnp discovery messages.  Since the IP address is assigned by DHCP, I can't rely on that to find them, plus the port they respond to can change any time the switch feels cranky.  So how the heck can I find them reliably ... arp seemed to work.  TCP/IP relies on the arp protocol to communicate, that should be reliable enough.  So, how to use it?

Fortunately, someone else has addressed this problem with a couple of tools, arping and arp-scan.  Arping relies on some knowledge in advance, but arp-scan doesn't.  You can issue an arp-scan on your home network and every machine on the network will be found.  Naturally, this isn't perfect, some devices guard against response to prevent hacking, and some devices don't respond on the first interaction.  But, I'm looking for Wemo switches, they respond, so now I have to figure out how to get the port number that the switch currently listens to.

Taking a chapter from my hacks on various other switches and devices, I decided to just try all the ports and see which one worked.  Since the switches I have only use ports 49152, 49153, and 49154, I can try all of them in a matter of seconds and not worry about it taking a ton of time searching.  If it turns out later that they go higher from time to time, I can simply add another port to the list.  Now, armed with this possibility, I tried out arp-scan.  Arp-scan is a normal linux tool, so all we have to do is install it:

sudo apt-get install arp-scan

And let the installation run.  Now we have to make it run as a root process because raw sockets need root permission to be effective:

sudo chmod +s /usr/bin/arp-scan

That will give the executable the 'suid' bit and since it's owned by root, it will get all the capabilities of a root process.  Nice, now run it and see what happens:

 arp-scan -q -v 192.168.0.0-192.168.0.255
Interface: eth0, datalink type: EN10MB (Ethernet)
Starting arp-scan 1.8.1 with 256 hosts (http://www.nta-monitor.com/tools/arp-scan/)
192.168.0.1     40:4a:03:de:41:87
192.168.0.2     00:1d:7e:91:12:0a
192.168.0.28    ec:1a:59:ce:82:71
192.168.0.34    4c:82:cf:83:c5:05
192.168.0.45    ec:e0:9b:b9:31:11
192.168.0.50    00:1d:7e:91:12:0a
192.168.0.29    ec:1a:59:ce:7c:8d
192.168.0.44    78:4b:87:41:5d:53
192.168.0.43    ec:1a:59:cd:99:55
192.168.0.22    2c:d0:5a:f6:dc:65
192.168.0.26    ec:1a:59:e8:fe:81
192.168.0.47    18:22:7e:d2:04:a0
192.168.0.202   de:ad:be:ef:fe:ed
192.168.0.203   de:ad:be:ef:fe:ee
192.168.0.204   de:ad:be:ef:fe:ef
---     Pass 1 complete
---     Pass 2 complete

18 packets received by filter, 0 packets dropped by kernel
Ending arp-scan 1.8.1: 256 hosts scanned in 1.258 seconds (203.50 hosts/sec). 15 responded

Notice a few things: I carefully chose the options to give me the information I needed while excluding the built-in capability of finding the manufacturer of the devices found by table lookup.  I was only interested in Belkin devices, and the table lookup depends on having up to date tables.  I really don't want to have to worry about keeping some table updated, so I'll handle that in the code for controlling the switch.  Also, arp-scan will take a range of addresses; since I limit my DHCP devices to the range 0-50, I should be able to scan all addresses in a few seconds.  That should be much, much faster than waiting for devices to respond to the (often ineffective) upnp discovery command.

The devices have been found, and a little code can isolate out the Belkin devices, so now it's time to find the port number the switch listens to.  This serves another purpose, to separate the switches from other Belkin devices that may show up over time.  It wouldn't do much good to tell a router or ethernet switch to turn off thinking it was a switch.  We've already discovered many intimate details of the switches including the location of the switch's setup xml description.  So a simple HTML request to switch-ip-address:possible-port/setup.xml should return a description of the switch.  Once we have the html in hand, just look inside it for the keywords and we know what we have.  I'm only interested in an Insite or a lightswitch; the other devices can wait for a later hack.  If nothing comes back, then I have the wrong port, just try the next one.  Here's an example of the setup.xml output that I sucked out of one of my switches:

<root xmlns="urn:Belkin:device-1-0"> <specVersion> <major>1</major> <minor>0</minor> </specVersion> <device> <deviceType>urn:Belkin:device:lightswitch:1</deviceType> <friendlyName>outsidegarage</friendlyName> <manufacturer>Belkin International Inc.</manufacturer> <manufacturerURL>http://www.belkin.com</manufacturerURL> <modelDescription>Belkin Plugin Socket 1.0</modelDescription> <modelName>LightSwitch</modelName> <modelNumber>1.0</modelNumber> <modelURL>http://www.belkin.com/plugin/</modelURL> <serialNumber>221332K130012B</serialNumber> <UDN>uuid:Lightswitch-1_0-221332K130012B</UDN> <UPC>123456789</UPC> <macAddress>EC1A59E8FE80</macAddress> <firmwareVersion>WeMo_WW_2.00.6395.PVT</firmwareVersion> <iconVersion>3|49153</iconVersion> <binaryState>0</binaryState> <iconList> <icon> <mimetype>jpg</mimetype> <width>100</width> <height>100</height> <depth>100</depth> <url>icon.jpg</url> </icon> </iconList> <serviceList> <service> <serviceType>urn:Belkin:service:WiFiSetup:1</serviceType> <serviceId>urn:Belkin:serviceId:WiFiSetup1</serviceId> <controlURL>/upnp/control/WiFiSetup1</controlURL> <eventSubURL>/upnp/event/WiFiSetup1</eventSubURL> <SCPDURL>/setupservice.xml</SCPDURL> </service> <service> <serviceType>urn:Belkin:service:timesync:1</serviceType> <serviceId>urn:Belkin:serviceId:timesync1</serviceId> <controlURL>/upnp/control/timesync1</controlURL> <eventSubURL>/upnp/event/timesync1</eventSubURL> <SCPDURL>/timesyncservice.xml</SCPDURL> </service> <service> <serviceType>urn:Belkin:service:basicevent:1</serviceType> <serviceId>urn:Belkin:serviceId:basicevent1</serviceId> <controlURL>/upnp/control/basicevent1</controlURL> <eventSubURL>/upnp/event/basicevent1</eventSubURL> <SCPDURL>/eventservice.xml</SCPDURL> </service> <service> <serviceType>urn:Belkin:service:firmwareupdate:1</serviceType> <serviceId>urn:Belkin:serviceId:firmwareupdate1</serviceId> <controlURL>/upnp/control/firmwareupdate1</controlURL> <eventSubURL>/upnp/event/firmwareupdate1</eventSubURL> <SCPDURL>/firmwareupdate.xml</SCPDURL> </service> <service> <serviceType>urn:Belkin:service:rules:1</serviceType> <serviceId>urn:Belkin:serviceId:rules1</serviceId> <controlURL>/upnp/control/rules1</controlURL> <eventSubURL>/upnp/event/rules1</eventSubURL> <SCPDURL>/rulesservice.xml</SCPDURL> </service> <service> <serviceType>urn:Belkin:service:metainfo:1</serviceType> <serviceId>urn:Belkin:serviceId:metainfo1</serviceId> <controlURL>/upnp/control/metainfo1</controlURL> <eventSubURL>/upnp/event/metainfo1</eventSubURL> <SCPDURL>/metainfoservice.xml</SCPDURL> </service> <service> <serviceType>urn:Belkin:service:remoteaccess:1</serviceType> <serviceId>urn:Belkin:serviceId:remoteaccess1</serviceId> <controlURL>/upnp/control/remoteaccess1</controlURL> <eventSubURL>/upnp/event/remoteaccess1</eventSubURL> <SCPDURL>/remoteaccess.xml</SCPDURL> </service> <service> <serviceType>urn:Belkin:service:deviceinfo:1</serviceType> <serviceId>urn:Belkin:serviceId:deviceinfo1</serviceId> <controlURL>/upnp/control/deviceinfo1</controlURL> <eventSubURL>/upnp/event/deviceinfo1</eventSubURL> <SCPDURL>/deviceinfoservice.xml</SCPDURL> </service> <service> <serviceType>urn:Belkin:service:smartsetup:1</serviceType> <serviceId>urn:Belkin:serviceId:smartsetup1</serviceId> <controlURL>/upnp/control/smartsetup1</controlURL> <eventSubURL>/upnp/event/smartsetup1</eventSubURL> <SCPDURL>/smartsetup.xml</SCPDURL> </service> <service> <serviceType>urn:Belkin:service:manufacture:1</serviceType> <serviceId>urn:Belkin:serviceId:manufacture1</serviceId> <controlURL>/upnp/control/manufacture1</controlURL> <eventSubURL>/upnp/event/manufacture1</eventSubURL> <SCPDURL>/manufacture.xml</SCPDURL> </service> </serviceList> <presentationURL>/pluginpres.html</presentationURL> </device> </root>

The tag <modelName>LightSwitch</modelName> tells me that this is a light switch, as opposed to an Insight, which would look like <modelName>Insight</modelName>.  This matters because the Insight switch returns a different value when it's turned on. The MAC address is in there, and a lot of other things that are used during operation.  For example to find the friendly name (the name assigned by the user) look at the tag <friendlyName>outsidegarage</friendlyName>.

The very fact that I got the XML back means I found the port and the tags tell me what it is, so now I have enough information, just some code to control the switches and I should be done.  Yeah, right.  When my victim er ... uh ... partner Glenn in this effort tested it, there were a number of problems that came up.  I had to put the IP address range in the .houserc file because there just wasn't any way to reliably discover the network range we were interested in.  Then, we found out that the wemo switches took to long to respond to the arp request and it had to be retried, sometimes several times to get them to talk.  When the switches decide to change addresses, there has to be a rediscovery to get the new port number.  You know, the usual list of things that will drive you nuts using a device differently than it was intended.

But, we overcame the ton of 'glitches' that almost appear to have been designed into the switch to keep people like me from doing things like this, and in the process made it much faster, much more reliable, and able to leap tall buildings with a single bound.  There are some caveats though:  The switches can still disappear.  Yes, even with a different method of discovery, and port change retries, the things will fail to respond long enough to evade detection.  To overcome this, the control process will simply exit after a few retries, and some controlling tool can automatically restart it to do the entire discovery process all over again.  This will find the switches and set them up to be used all over again.  A simple script like this:

#!/bin/bash
while : do         echo "Starting wemoccontrol"         wemocontrol.py         sleep 10 done

can restart the process.  Yes, you have to replace the commands with something suitable to your environment, and the sleep is to keep it under control in case you make a mistake, but you can use this to test the idea.  I use upstart and a config file to accomplish this for all the processes I run on the Pi, why invent something new when a great tool already exists?

So, instead of using upnp discovery, I switched to arp discovery, I find the Belkin device, and among those I try the various ports to discover Wemo light switches and Insite devices, then I bring up a CherryPy server to control them.  When they fail to respond to a request, I try three more times to make them respond and then give up and let the process die to be restarted by some other control software.  The controls are the same as my previous tries, I record the state in a Sqlite3 database and that in turn can be read by anything running on the machine.  Sounds pretty complicated, but it's much, much simpler than the other techniques I came up with.  This is very lightweight and works quite a bit faster than the others, and so far, much more reliably.  Here's the code, and it has also been placed in Github to make it easier to grab if you want <link>.

#! /usr/bin/python
# Checking Wemo Switches
#
import subprocess
import commands
from datetime import datetime, timedelta
import time
import urllib2
import BaseHTTPServer
from socket import *
import sys
import json
import re
import argparse
import sqlite3
import cherrypy
from houseutils import lprint, getHouseValues, timer, checkTimer

#--------This is for the HTML interface 
def openSite(Url):
    #lprint (Url)
    webHandle = None
    try:
        webHandle = urllib2.urlopen(Url, timeout=2) # give up in 2 seconds
    except urllib2.HTTPError, e:
        errorDesc = BaseHTTPServer.BaseHTTPRequestHandler.responses[e.code][0]
        #print "Error: (opensite) cannot retrieve URL: " + str(e.code) + ": " + errorDesc
        raise
    except urllib2.URLError, e:
        #print "Error: (openSite) cannot retrieve URL: " + e.reason[1]
        raise
    except:  #I kept getting strange errors when I was first testing it
        e = sys.exc_info()[0]
        #print ("(opensite) Odd Error: %s" % e )
        raise
    return webHandle

def talkHTML(ip, command):
    website = openSite("HTTP://" + ip + '/' + urllib2.quote(command, safe="%/:=&?~#+!$,;'@()*[]"))
    # now (maybe) read the status that came back from it
    if website is not None:
        websiteHtml = website.read()
        return  websiteHtml
        
# and this is for the SOAP interface        
# Extract the contents of a single XML tag from the data
def extractSingleTag(data,tag):
    startTag = "<%s" % tag
    endTag = "</%s>" % tag

    try:
        tmp = data.split(startTag)[1]
        index = tmp.find('>')
        if index != -1:
            index += 1
            return tmp[index:].split(endTag)[0].strip()
    except:
        pass
    return None

def sendSoap(actionName, whichOne, actionArguments):
    argList = ''
    soapEnd = re.compile('<\/.*:envelope>')
    if not actionArguments:
        actionArguments = {}
    for item in switches:
        if item["name"] == whichOne:
            thisOne = item
            break;
    switchIp = item["ip"]
    switchPort = item["port"]
    
    for arg,(val,dt) in actionArguments.iteritems():
        argList += '<%s>%s</%s>' % (arg,val,arg)

    soapRequest = 'POST /upnp/control/basicevent1 HTTP/1.1\r\n'
    # This is the SOAP request shell, I stuff values in it to handle
    # the various actions 
    # First the body since I need the length for the headers
    soapBody =  '<?xml version="1.0"?>\n'\
            '<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">\n'\
            '<SOAP-ENV:Body>\n'\
            '\t<m:%s xmlns:m="urn:Belkin:service:basicevent:1">\n'\
            '%s\n'\
            '\t</m:%s>\n'\
            '</SOAP-ENV:Body>\n'\
            '</SOAP-ENV:Envelope>' % (actionName,argList,actionName)

    #These are the headers to send with the request
    headers =   {
            'Host':'%s:%s' % (switchIp, switchPort),
            'Content-Length':len(soapBody),
            'Content-Type':'text/xml',
            'SOAPAction':'"urn:Belkin:service:basicevent:1#%s"' % (actionName)
            }
    #Generate the final payload
    for head,value in headers.iteritems():
        soapRequest += '%s: %s\r\n' % (head,value)
    soapRequest += '\r\n%s' % soapBody
    if showXml:
        print stars
        print "***REQUEST"
        print soapRequest
 
    try:
        sock = socket(AF_INET,SOCK_STREAM)
        sock.connect((switchIp,int(switchPort)))
        sock.settimeout(3);  # don't want to hang forever, ever
        sock.send(soapRequest)
        soapResponse = ""
        while True:
            data = sock.recv(1024)
            if not data:
                break
            else:
                soapResponse += data
                if soapEnd.search(soapResponse.lower()) != None:
                    break
        if showXml:
            print "***RESPONSE"
            print soapResponse
            print stars
            print ''
        sock.close()
        (header,body) = soapResponse.split('\r\n\r\n',1)
        if not header.upper().startswith('HTTP/1.') and ' 200 ' in header.split('\r\n')[0]:
            print 'SOAP request failed with error code:',header.split('\r\n')[0].split(' ',1)[1]
            errorMsg = self.extractSingleTag(body,'errorDescription')
            if errorMsg:
                print 'SOAP error message:',errorMsg
            return None
        else:
            return body
    except Exception, e:
        lprint ('Caught exception in sending:', e, switchIp, switchPort)
        sock.close()
        return None
    except KeyboardInterrupt:
        print "Keyboard Interrupt"
        sock.close()
        return None

# This will look at the result from sendSoap, and if the
# switch disappeared, it will try and get the new port number
# and update the various items.  This should allow the code 
# to continue as if the switch never decided to change its
# port number
def sendCommand(actionName, whichOne, actionArguments):
    result = sendSoap(actionName, whichOne, actionArguments)
    if result is not None:
        return result
    # it failed, now we have to do something about it
    # first, get the switch entry to check for a port change
    for item in switches:
        if item["name"] == whichOne:
            thisOne = item
            break;
    switchIp = item["ip"]
    switchPort = item["port"]
    # try to get the port number from the switch a few times
    for i in range(0,3): # Only try this three times
        lprint ("Trying to recover the switch %s"%whichOne)
        # getPort doesn't use sendSoap, so this call won't recurs
        newEntry = getPort(switchIp)
        # if the port changed, try and get the new one
        if newEntry is not None:
            # fine, it's at least alive, grab the port number,
            # print something, and and stuff it in the database
            # if it didn't change this won't break it, but if 
            # it did change, this will fix it.
            item["port"] = newEntry["port"]
            lprint ("Switch", whichOne, "changed ip from", switchPort, "to", newEntry["port"])
            dbconn = sqlite3.connect(DATABASE)
            c = dbconn.cursor()
            try:
                c.execute("update lights " 
                    "set port=? where name = ?;",
                    (newEntry["port"], whichOne))
            except sqlite3.OperationalError:
                lprint("Database is locked, record skipped")
            dbconn.commit()
            dbconn.close()
            # now try the command again
            # if it died completely it may have come back by now,
            # or if the port changed, this will try it one more time
            # it needs a limit this because this call will recurs
            result = sendSoap(actionName, whichOne, actionArguments)
            if result is not None:
                lprint("Switch recovered")
                return result
            time.sleep(1) #give the switch time to catch its breath
        else: 
            # this means the switch is not responding to HTML
            # so try the getPort again to see if it's back yet
            # There's no point in sending the soap command yet
            time.sleep(1) #give the switch time to catch its breath
            continue
    # it failed three times, just give up, die and let the system
    # restart the process.
    exit("The switch %s went away"% whichOne)
        
        
# Step through each light and see get its current state
# then record the state in the database.
def doLights():
    for switch in switches:
        thisOne = switch['name']
        updateDatabase(thisOne,get(thisOne))

def keepAlive():
    '''
    I update the database periodically with the time so I can check to see 
    if things are holding together.  I currently use the time in the light 
    switch records for this.
    '''
    lprint(" keep alive")
    for switch in switches:
        thisOne = switch['name']
        updateDatabase(thisOne, get(thisOne), force=True)

        
def get(whichone):
    ''' 
    Returns On or Off
    '''
    resp = sendCommand('GetBinaryState', whichone, {})
    if resp is not None:
        tagValue = extractSingleTag(resp, 'BinaryState').split('|')[0]
        return 'Off' if tagValue == '0' else 'On'
    return 'Off'

def on(whichone):
    """
    BinaryState is set to 'Error' in the case that it was already on.
    """
    resp = sendCommand('SetBinaryState', whichone, {'BinaryState': (1, 'Boolean')})
    if resp is not None:
        tagValue = extractSingleTag(resp, 'BinaryState').split('|')[0]
        status = 'On' if tagValue in ['1', '8', 'Error'] else 'Off'
        handleUpdate(whichone, status)
        lprint("turned %s on"%(whichone))
        return status
    return 'Off'

def off(whichone):
    """
    BinaryState is set to 'Error' in the case that it was already off.
    """
    resp = sendCommand('SetBinaryState', whichone, {'BinaryState': (0, 'Boolean')})
    if resp is not None:
        tagValue = extractSingleTag(resp, 'BinaryState').split('|')[0]
        status = 'Off' if tagValue in ['0', 'Error'] else 'On'
        handleUpdate(whichone, status)
        lprint("turned %s off"%(whichone))
        return status
    return 'Off'
    
def toggle(whichOne):
    if (get(whichOne) == 'On'):
        off(whichOne)
    else:
        on(whichOne)
        
def outsideLightsOn():
    lprint (" Outside lights on")
    on("outsidegarage")
    on("frontporch")
    on("cactusspot")
    
def outsideLightsOff():
    lprint (" Outside lights off")
    off("outsidegarage")
    off("frontporch")
    off("cactusspot")

        
def handleUpdate(whichone, status):
    for i in switches:
        if i['name'] == whichone:
            i['status'] = status
    updateDatabase(whichone, status)
    
def updateDatabase(whichone, status, force=False):
    ''' 
    This is running on a Pi and is not event driven, so polling like
    this will result in considerable wear to the SD card.  So, I'm going to 
    read the database to see if it needs to be changed before I change it.  
    According to everything I've read, reads are free, it's the writes that
    eat up the card.
    '''
    dbconn = sqlite3.connect(DATABASE)
    c = dbconn.cursor()
    c.execute("select status from lights where name = ?;",
        (whichone,))
    oldstatus = c.fetchone()
    if oldstatus[0] != status or force == True:
        lprint ("Had to update database %s, %s"%(whichone, status))
        try:
            c.execute("update lights " 
                "set status = ?, utime = ? where name = ?;",
                (status, time.strftime("%A, %B, %d at %H:%M:%S"), whichone))
            dbconn.commit()
        except sqlite3.OperationalError:
            lprint("Database is locked, record skipped")
    dbconn.close()

# If a command comes in from somewhere, this is where it's handled.
def handleCommand(command):
    lprint(str(command))
    # the command comes in from php as something like
    # ('s:17:"AcidPump, pumpOff";', 2)
    # so command[0] is 's:17:"AcidPump, pumpOff'
    # then split it at the "  and take the second item
    try:
        c = str(command[0].split('\"')[1]).split(',')
    except IndexError:
        c = str(command[0]).split(' ')    #this is for something I sent from another process
    lprint(c)
    if (c[0] == 'OutsideLightsOn'):
        outsideLightsOn()
    elif (c[0] == 'OutsideLightsOff'):
        outsideLightsOff()
    elif (c[0] == 'fPorchToggle'):
        toggle("frontporch")
    elif(c[0] == 'garageToggle'):
        toggle("outsidegarage")
    elif (c[0] == 'cactusToggle'):
        toggle("cactusspot")
    elif (c[0] == 'patioToggle'):
        toggle("patio")
    else:
        lprint("Weird command = " + str(c))

# First the process interface, it consists of a status report and
# a command receiver.
class WemoSC(object):
    @cherrypy.expose
    @cherrypy.tools.json_out() # This allows a dictionary input to go out as JSON
    def status(self):
        status = []
        for item in switches:
            status.append({item["name"]:get(item["name"])})
        return status
        
    @cherrypy.expose
    def pCommand(self, command):
        handleCommand((command,0));
        
    @cherrypy.expose
    def index(self):
        status = "<strong>Current Wemo Light Switch Status</strong><br /><br />"
        for item in switches:
            status += item["name"] +" is " + get(item["name"]) + "&nbsp;&nbsp;"
            status += '<a href="wemocommand?whichone='+item["name"]+'"><button>Toggle</button></a>'
            status += "<br />"
        return status
        
    @cherrypy.expose
    def wemocommand(self, whichone):
        # first change the light state
        toggle(whichone)
        # now reload the index page to tell the user
        raise cherrypy.InternalRedirect('/index')

# given the ip of a Belkin device this will try the ports that
# are used on the Wemo switches to see which one works.  The assumption
# is that if none of the ports work, it's not a switch, it's a modem or
# something else.
def getPort(ip):
    entry = []
    for p in ["49153", "49154", "49155"]:
        try:
            resp = talkHTML(ip + ':' + p + "/setup.xml", "")
            if debug:
                print "\tfound one at", b[0], "port", p
            if showXml:
                print stars
                print "response from switch"
                print resp
                print stars
            name = extractSingleTag(resp, 'friendlyName')
            model = extractSingleTag(resp, 'modelName')
            entry = {"mac":b[1],"ip":b[0], "port":p, "name":name, "model":model}
            return entry
        except timeout:
            continue
        except urllib2.URLError:
            continue
        except:
            e = sys.exc_info()[0]
            print ("Unexpected Error: %s" % e )
            continue
    return None
        
####################### Actually Starts Here ################################    
debug = False
showXml = False
if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("-d", "--debug",
        action = "store_true",
        help='debug flag')
    parser.add_argument("-x", "--xml",
        action = "store_true",
        help='show xml')
    parser.add_argument('count',type=int);
    args = parser.parse_args()
    if args.debug:
        print "Running with debug on"
        debug = True
    if args.xml:
        print "Running with showXML on"
        showXml = True
    targetNumber = args.count

    stars = "*********************************************"

    #-------------------------------------------------
    # the database where I'm storing stuff
    DATABASE=getHouseValues()["database"]
    lprint("Using database ", DATABASE);
    # Get the ip address and port number you want to use
    # from the houserc file
    ipAddress=getHouseValues()["wemocontrol"]["ipAddress"]
    port = getHouseValues()["wemocontrol"]["port"]
    lprint("started looking for {} switches".format(targetNumber))

    # This works on my machine, but you may have to mess with it
    # The arp-scan below tells it not to look up the manufacturer because I
    # didn't want to worry about keeping the tables that are used up to date,
    # the -l tells it to find the local net address on its own, and 
    # -v (verbose) will print that net address so I can show it for debugging
    # I take the scan range out of the .houserc file, it's an entry under wemocontrol
    # that looks like "scanRange":"192.168.0.1-192.168.0.50" adjust this as
    # needed
    try:
        scanRange = getHouseValues()["wemocontrol"]["scanRange"]
        arpCommand = "arp-scan -q -v %s 2>&1" %(scanRange)
    except KeyError:
        print "No entry in .houserc for wemocontrol scanRange"
        exit();

    while True:
        devices = [];
        # first the devices on the network
        if debug:
            print "arp-scan command is:", arpCommand
        theList = subprocess.check_output(arpCommand,shell=True);
        # split the output of the arp-scan into lines instead of a single string
        lines = theList.splitlines()
        # this looks at each line and grabs the addresses we're interested in
        # while ignoring the lines that are just information.
        for line in lines:
            allowedDigits = set("0123456789abcdef:. \t")
            if all(c in allowedDigits for c in line):
                d = line.split()
                try:
                    devices.append([d[0], d[1]])
                except IndexError: # an empty line will pass the test
                    continue
        # arp-scan can give the same addresses back more than once
        # step through the list and remove duplicates
        temp = []
        for e in devices:
            if e not in temp:
                temp.append(e)
        devices = temp
        if debug:
            print devices
        # for each device, look up the manufacturer to see if it was registered
        # to belkin
        bDevices = []
        # I got this list direct from the IEEE database and it may
        # need to be updated in a year or two.
        belkinList = ("001150", "00173F", "001CDF", "002275", "0030BD", 
                        "08863B", "94103E", "944452", "B4750E", "C05627", "EC1A59")
        for d in devices:
            if d[1].replace(':','')[0:6].upper() in belkinList:
                    bDevices.append([d[0],d[1]])
        if debug:
            print "These are the Belkin devices on the network"
            print bDevices
        if len(bDevices) < targetNumber: 
            lprint ("Only found", len(bDevices), "Belkin devices, retrying")
            time.sleep(1)
            continue
        # Got all that were asked for, continue to the next step
        
        # Now that we have a list of the Belkin devices on the network
        # We have to examine them to be sure they are actually switches
        # and not a modem or something else.  This will also assure that 
        # they will actually respond to a request.  They still may not work,
        # but at least we have a chance.
        switches = []
        for b in bDevices:
            result = getPort(b[0])
            if result is not None:
                switches.append(result)
        # Did we find enough switches ?
        if len(switches) < targetNumber: 
            lprint ("Only found", len(switches), "of them, retrying")
            devices = []
            continue
        # Yes we did, break out.
        break;
    # Now I'm going to check the database to see if it has been
    # adjusted to hold all the items (older version didn't have
    # ip, port, and mac addresses
    dbconn = sqlite3.connect(DATABASE)
    c = dbconn.cursor()
    c.execute("pragma table_info(lights);")
    dbrow = c.fetchall()
    if not any('ip' and 'mac' and 'port' in r for r in dbrow):
        lprint ("Database needs to be adjusted")
        lprint ("to hold ip, port, and MAC")
        try:
            print "adding ip if needed"
            c.execute("alter table lights add column ip text;")
        except sqlite3.OperationalError:
            print "ip was already there"
        try:
            print "adding mac if needed"
            c.execute("alter table lights add column mac text;")
        except sqlite3.OperationalError:
            print "mac was already there"
        try:
            print "adding port if needed"
            c.execute("alter table lights add column port text;")
        except sqlite3.OperationalError:
            print "port was already there"
        dbconn.commit()
    else:
        lprint ("Database already adjusted")
    for item in switches:
        try:
            c.execute("update lights " 
                "set ip = ?, mac=?, port=?where name = ?;",
                (item["ip"], item["mac"], item["port"], item["name"]))
            dbconn.commit()
        except sqlite3.OperationalError:
            lprint("Database is locked, record skipped")
    dbconn.commit()
    dbconn.close()
    lprint ("")
    lprint ("The list of", len(switches), "switches found is")
    for item in switches:
        lprint ("Friendly name:", item["name"])
        lprint ("Model:", item["model"])
        lprint ("IP address:", item["ip"])
        lprint ("Port number:", item["port"])
        lprint ("MAC:", item["mac"])
        lprint ('')
        
    # timed things.
    checkLightsTimer = timer(doLights, seconds=2)
    keepAliveTimer = timer(keepAlive, minutes=4)
    # Now configure the cherrypy server using the values
    cherrypy.config.update({'server.socket_host' : ipAddress.encode('ascii','ignore'),
                            'server.socket_port': port,
                            'engine.autoreload.on': False,
                            })
    # Subscribe to the 'main' channel in cherrypy with my timer
    cherrypy.engine.subscribe("main", checkTimer.tick)
    lprint ("Hanging on the wait for HTTP message")
    # Now just hang on the HTTP server looking for something to 
    # come in.  The cherrypy dispatcher will update the things that
    # are subscribed which will update the timers so the light
    # status gets recorded.
    cherrypy.quickstart(WemoSC())
    
    sys.exit("Told to shut down");

Notice that it is self contained.  It doesn't require Miranda.py anymore, nor any of the Ouimeaux libraries.  That's what makes it appealing to me; it's nice to have everything in one module and reasonably easy to understand.  I added a few parameters:

wemocontrol.py -h
usage: wemocontrol.py [-h] [-d] [-x] count

positional arguments:
  count

optional arguments:
  -h, --help   show this help message and exit
  -d, --debug  debug flag
  -x, --xml    show xml

I start it with wemocontrol.py 4, because I have four switches.  It will keep looking until it finds all four switches which could be a problem if one of them is disconnected, but I'll deal with that problem as it happens.  The -x parameter displays the SOAP messages as they are sent and received.  This can be used to find problems in the protocol or to understand how it works.  The -d is for debugging when you can't get things to work.  If you turn them both on, the volume of output is huge, so be prepared for it.  The -h is because I can never remember this stuff and wanted a reminder.  To run it, you must have an entry in a file called .houserc in the home directory of the user.  I use this file for all the entries for my processes.  This way I can keep things like keys, directory paths, etc in one place easily found.  The entries I have look like this:

"wemocontrol":{
"ipAddress":"192.168.0.205",
"port": 51001,
"scanRange":"192.168.0.1-192.168.0.50"},

I have a complete description of the file and how to use it here <link>.

Now, I must thank Glenn for his help in this effort.  This thing would have been a bear to test without him.  The rest of you, have fun with 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