25 December 2009

LG Washer LE Error

UPDATE:  I've had quite a few hits on this posting and received lots of great comments - thanks very much.

This post explains how to replace the hall sensor on your LG washer.  Before you decide that you have to replace your hall sensor, please try unplugging your washer for 5-10 minutes and see if it will reset all on its own.

Also, even after replacing the sensor, sometimes these washers will display "LE" a week or month later and they just need a time-out (unplugged).  Good luck!

I've also had a few questions on what the heck is a hall sensor. Here's a whitepaper with a good description, design notes and ideas on what else you can do with these things.

-------------------------------------------------------------------------

We had a really nice Maytag washer and dryer set. They were a billion years old (maybe 15 actually) but ran strong and had zero problems. Spring 2007 we decided to remodel our laundry room and of course we had to have the super duper LG WM2075CW 27" Front-Load Washer with 3.72 cu. ft. Capacity, 7 Cycles, 6 Options, SenseClean and 8 Hours Delay Wash.

About $800 delivered. Nothing but the best for my socks and undies (boxers btw).

Everything has been great except the week before Christmas 2009, Mr LG wouldn't work. It would grunt and click and then put "LE" on the display. A quick Google yields lots and lots and lots of hits with the Load Error (LE).

A number of things can cause an LE but the most common is a malfunctioning Hall Sensor. Seems that LG washers are pieces of crap and this is a well known problem. LG has extended the warranty on the part to 7 years but they won't ship the in-warranty part to consumers. They'll only ship to a certified service company. After several phone calls to LG and way too many minutes on hold, I finally just ordered the damn thing from MCM Electronics, part number 6501KW2002A for $18. Plus tax and shipping, at my door for $29.75.

Actually the LG washer and dryer have been great, I'm just very ticked that LG has a known problem but won't do a recall to fix the part with a more reliable piece. And even more ticked that they won't ship me an in-warranty part so I can do it myself. So for now on, I will refer to the washer as a piece of crap and LG as a seller of crap. My next washer will be a Bosch.


The Google hit that explained the error, the part number and how to replace is FixYa. Their how-to was a little brief and a picture is worth a thousand so I thought I would help out the next sucker that also has a piece of crap that needs to be fixed.

Read thru that FixYa article and make a decision if your problem is also the Hall Sensor. If so, follow along.

This is a pretty simple fix. If you can change a sparkplug than you can easily do this one. (I compared this to a sparkplug change cause you'll get a little dirty, gotta pull some cables, and work a socket wrench).

You'll need a phillips head screw driver, 17mm and 10mm sockets or wrenches.

A quick note:  Even though the pictures are somehow dated 1/7/2006, they were actually taken in December 2009.  Someone reset my camera date and I didn't notice until pics were all taken.

Ok, First step, UNPLUG the washer. And carefully move the washer so that you can access the back panel. If your washer and dryer are stacked, please be careful and make sure there is nothing on top that will fall and hit you in the head. Ask someone to help you, don't blame me if you hurt your self moving the thing. Be careful!
  • Remove the 4 phillips head screws on the back panel
  • With a 17mm socket, remove the bolt that holds on the motor rotor. Turn the bolt to the left (counter clockwise) to remove. The drum will probably turn as you're trying to get the bolt loose so with your free hand, grab the rotor around 10 o'clock and hold tight while you turn the bolt. Be patient and it'll come right out.
  • Next,you need to pull the motor rotor off to expose the stator. This will be a little tough because of the magnets and a tight fit. Use both hands on opposite sides and gently tug one side, then the other, then both. Try your hands at 1 o'clock and 7 o'clock. Then switch to 5 and 11. Be patient and tug gently. Watch the white center and you'll see it slowly pull away from the shaft.


  • Set the rotor to the side and remove the 10mm bolts holding the stator on. I wasn't sure what to expect when I started removing it and I wanted to make sure I put it on right side up so I made pencil marks to line things back up. These really aren't needed cause it'll be obvious as to which side is up.
  • When you remove the 6th bolt, use your free hand to hold on to the stator cause it will fall right off. There are two connectors at the bottom, one the goes to the motor windings, the other to the hall sensor that we're replacing. If you don't hold onto the stator, you might damage the wires or connectors. Go slow on the last bolt and hang on.
  • Here's what those connector look like from the bottom side. I didn't have any luck trying to get them free from the bottom though. I was kinda squeezed in between the wall and washer so I didn't have much room to wiggle. I let the stator lean towards me like the picture above and then gently pulled both connectors free. Don't go Rambo on these connectors, you don't need to replace extra stuff. Breathe deep and squeeze the little tabs, they'll come free.
  • Almost there. That shiny looking thing on the bottom right of the stator ring is the hall sensor. It's held on with 3 clips on the back side, 1 clip on the front. Simple pry up the 1 clip and it will pop free.
  • The new sensor snaps back on just like you'd expect. Hook up the 3 clips first and then snap it on to the 1 clip side.
  • I don't have pictures of these next steps cause it's just the opposite of the above. Just look at the pictures in the reverse order and it'll all go right back together.
  • Hook up the two connectors. Make sure they click all the way back on.
  • Line the stator back up with the bolt holes and screw the bolts back in with a 10mm socket. Tighten them firm but don't over do it.
  • Push the rotor back onto the shaft. Because of the magnets and the shaft grooves, it will feel like it's not going. It will, line it up and apply pressure at the edges.
  • Tighten the big bolt with a 17mm socket. As it tightens, the drum will start to rotate. Grab the edge with your free hand and tighten firmly.
  • Put the back panel back onand tighten down the 4 phillips head screws.
  • Plug the washer back in and carefully push the washer back into place.
  • Test.
  • Your piece of crap washer is fixed.
Total time was 38 minutes. If I didn't stop and take pictures, easily under 30 minutes.

What do you think? Leave a comment, buy me a beer!



15 May 2009

Porsche Boxster Cabriolet

Sometimes you just have to give in. I tried to be strong but night after night. Day after day. All I heard was "buy me, buy me". How much is a man expected to take? We can only be so strong before we all break down.

It was hard, but I relented and came home with a Boxster. It's my combo birthday present and mid-life crisis consolation prize.

As my old age time bomb has been approaching over the last year, I've been thinking about the 40th anniversary Camaro.

I had a 1969 Camaro in my Senior year of high school. During the first semester, a girl in my first period class was driving her brother's 1965 Mustang to school and somehow drove her front bumper all the way into my backseat. And then tried to hit-n-run. Since the cars had become melded into one, she didn't get very far but she sure did try.

Anyway, I've been thinking a lot about the new Camaros. But with all the trouble GM has been having, I'm just not excited about paying $10K premium to a company that's on the verge of bankrupcy. And the last few years of Chevrolets really haven't seemed like quality cars. Probably not a good choice.

So I've been cruising Craigslist and eBay looking at other sports cars that I've wanted since I was 13. High on the list was a Datsun 280Z. Man, I wanted one of those so bad back then. When I look at them now, they're kinda cheesy. Not sure what was driving my desire back then. Maybe puberty.

Last week, I thought I had found the car. A Porsche Boxster Cabriolet. Price was right. Bank said yes. And boss said yes. Yes! No. The owner said it had been in an accident and the hood was replaced. That's not bad really. I mean as long as nothing was seriously crunched, for the right price, that wouldn't bother me. The owner said unless he hadn't told me, I'd never know. Said it was a perfect match/fit and there was no lingering problems. Then the night before I was to drive to Chicago to see it, it was disclosed that the frame "was bent or had been bent". What? I talked to the guy a dozen times, emailed a dozen more. And the night before I see it, it's "disclosed"? No way, wasn't going to waste any more time on that dream.

The idea of a Boxster Roadster stayed with me though. And Mr Craiglist led me to the next beautiful car. The Great White.

One word to sum up the car. Wow.

I love it. It is so clean. Perfect interior and exterior. Looks brand new. Nice throaty rumble, comfy leather seats, decent audio. Drives so nice.

When I told the kids that I spent their college funds and they'd have to attend community colleges now, my daughter said. "That's okay. Will you drive me to school tomorrow?"



Update: Sad Day for the Great White Boxster

02 May 2009

Greasemonkey Index of /images

After I posted the Yahoo Baseball Greasemonkey script, I remembered another script that I have that's worth sharing. Not my Yahoo Football Greasemonkey script which does the same as the baseball (shows in-game stats on tab title) but another more useless useful script.

Have you ever been searching for an image or been Stumbling around and came across a page like the one to the left?

I see them quite often actually because I'm nosy and like to poke around on web sites. If I see a picture of interest, I wonder if the web site owner has others that aren't being displayed.

But here's the rub. We have a nice list but we have to click each one to see it.

Greasemonkey to the rescue.

I wrote a quick script that replaces the little icon to the left of each link with the link src. And since loading these images may be time consuming, you don't want to do this on every "Index of /" page that you come across. So we wrap that function up in a "Show Images" link at the top of the page when it completes loading.

I've uploaded the script to userscripts.org where you can check it out. Install it and go to a page where you can see for yourself how it works.

Google around for "index of /images" and see what treasures abound.

I find lots of pages like this by "going up" a level on a URL. For example (not real, this is not a valid location) if you are looking at a photo:

http://somewhere.good/images/138028.jpg

Then see what's at:

http://somewhere.good/images

Either by editing the URL in the address bar or use the "up one level on this web site" button in Firefox. What, you don't know about that button? Right-Click an empty spot the tool bar and select "Customize...". Drag the icon that looks like the one I've circled below to your tool bar. Use it and discover lots of stuff.


What do you think? Leave a comment.

Baseball Better - Yahoo Greasemonkey Script

I love baseball. But you gotta admit that it can be boring "watching" a game on the web. If not watching it in person or on tv, it's kinda slow. I'm sorta ADD when cruising the web. I usually have 6 or more Firefox tabs open at once. I'm reading something, writing something, searching for something all at the same time. It's a wonder that I get anything done at all, but amazingly it works out.

So if a good game is on at the same time that I'm on-line, I like to have a tab tuned into the Yahoo Sport MLB game. But the problem is that I have to keep clicking to that tab to see what's going on. I have Greasemonkey installed and have written a few other scripts to customize my viewing pleasure so hacking the box score page should be pretty easy.

Here's what the regular game looks like. Usually there's an advertisement over on the right side above the "series at glance" table. I guess times are tough at Yahoo and advertising is slow because no ad is being displayed on this screen shot.

Notice a few other things. The Firefox tab says "MLB - Kansas City Royals/M...". I'd like this to display something useful like the current in-progress game stats. Also see the Inning Summary table at the bottom. Below that is a Scoring Summary that can't be seen unless you scroll down.

So my typical game "watching" experience is every 5-10 minutes, click the tab, look at the score. If different than last time I looked, grab the scroll bar and go way down to see what happened. Way too much work.

After some quick Greasemonkey hacking, I've come up with a better experience. As the page loads, I grab the team names, current score, inning, ball-strike count, runners on-base, and last play. Sounds like a lot but really it's not. Yahoo has everything in nice tables and getting it is like butter. Not really but I just felt like using that term. Just like buddah.

I take all those stats and format them into the tab so I can see the game info without having to click the tab. This alone has probably saved years of wear and tear on my mouse button.

I then replaced the advertisement block (yahoo isn't currently using it anyway) with the scoring summary so no painful scrolling occurs.

For those that also want to enjoy baseball a little better, I've uploaded the script to Greasemonkey script haven.

For those that don't trust me and want to see what I've done, here's a view in the sausage factory. It ain't pretty but it works.

It's just basically screen scraping by searching for classes and then parsing data. Every time Yahoo makes a tweak, it breaks the script. I don't mind, it's usually minor.

Here we go.

Yahoo sets the page to a fixed width of 974 pixels. I run in 1280 mode so this wastes lots of valuable screen space and causes unsightly white gutters. This must be changed:
function changeWidth() {
var node = document.evaluate('//table[@width="974"]',
document,
null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
null);

node.snapshotItem(0).width="100%";
}
Here's a big part of the magic. Find the table that wraps the team name and score and parse it out. The team names are long and we need shorten them so they fit it the tab nicely.
function getScore() {
var scoreNode = document.evaluate(
'//td[@class="yspsctnhdln"]',
document,
null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
null);
scoreNode = (scoreNode.snapshotItem(0));

// this is ugly but I want to shorten the team so all the info displays nice on the tab
var str = scoreNode.textContent;
str = str.replace(/\n/g, "");
str = str.replace(/ /g, "");
str = str.replace(/,/g, "");
str = str.replace("Arizona", "AZ");
str = str.replace("Atlanta", "Atl");
str = str.replace("Baltimore", "Bal");
str = str.replace("Boston", "Bos");
str = str.replace("Chi Cubs", "Cub");
str = str.replace("Chi White Sox", "CWS");
str = str.replace("Cincinnati", "Cin");
str = str.replace("Cleveland", "Cle");
str = str.replace("Colorado", "Col");
str = str.replace("Detroit", "Det");
str = str.replace("Florida", "FL");
str = str.replace("Houston", "Hou");
str = str.replace("Kansas City", "KC");
str = str.replace("Minnesota", "Min");
str = str.replace("Milwaukee", "Mil");
str = str.replace("LA Angels", "LAA");
str = str.replace("LA Dodgers", "LAD");
str = str.replace("NY Mets", "Met");
str = str.replace("NY Yankees", "NYY");
str = str.replace("Oakland", "Oak");
str = str.replace("Philadelphia", "Phi");
str = str.replace("Pittsburgh", "Pit");
str = str.replace("San Diego", "SD");
str = str.replace("San Francisco", "SF");
str = str.replace("Seattle", "Sea");
str = str.replace("St. Louis", "SL");
str = str.replace("Tampa Bay", "Tam");
str = str.replace("Toronto", "Tor");
str = str.replace("Texas", "Tex");
str = str.replace("Washington", "Was");
str = str.replace(/ /g, "");

// get inning and shorten
var inning = getInning();
inning = inning.replace(/\n/g, "");
inning = inning.replace(/ /g, "");
inning = inning.replace(/Bot /g, "B");
inning = inning.replace(/Top /g, "T");
inning = inning.replace(/End /g, "E");

document.title = str + ' ' + inning + ' ' + getOut();
}
In the above code, I didn't put a comment on this line but this is where the Firefox tab gets the in-game stats:
document.title = str + ' ' + inning + ' ' + getOut();
And more parsing. And then some really ugly if-else code. Kinda embarrassing that I did this. But I did and I'm too lazy to re-write.
function getOut() {
var balls = document.evaluate("//*[contains(.,'O:')]/b",
document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;

var count = "";
if (balls) {
var n= balls.nextSibling;
var strikes = n.nextSibling;
n = strikes.nextSibling;
var outs= n.nextSibling;
count = balls.innerHTML + '-' + strikes.innerHTML + ' O:'+ outs.innerHTML;
}

// find how many men are on base.
// do this by looking at what image is being displayed.
// yeah, I need to clean this up but it works and I don't have time. go for it
var men = document.evaluate("//img[contains(@src,'tr_empty.gif')]",
document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
if (men) {
men = "empty";
}
else {
men = document.evaluate("//img[contains(@src,'tr_1b.gif')]",
document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;

if (men) {
men = "1b";
}
else {
men = document.evaluate("//img[contains(@src,'tr_2b.gif')]",
document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
if (men) {
men = "2b";
}
else {
men = document.evaluate("//img[contains(@src,'tr_3b.gif')]",
document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
if (men) {
men = "3b";
}
else {
men = document.evaluate("//img[contains(@src,'tr_1b2b.gif')]",
document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
if (men) {
men = "1b2b";
}
else {
men = document.evaluate("//img[contains(@src,'tr_1b3b.gif')]",
document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
if (men) {
men = "1b3b";
}
else {
men = document.evaluate("//img[contains(@src,'tr_2b3b.gif')]",
document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
if (men) {
men = "2b3b";
}
else {
men = document.evaluate("//img[contains(@src,'tr_full.gif')]",
document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
if (men) {
men = "full";
}
else {
men = "";
}
}
}
}
}
}
}
}
return count + ' ' + men;
}
And that's about it. Take a look at the file at userscripts.org and give it a try. Let me know if you've improved and found some bugs.

What do you think? Leave a comment.

08 April 2009

XBee Series 2 - Reading Data in API Mode

After I finally got a ZNET 2.5 Coordinator AT associated with a ZNET 2.5 ROUTER / END DEVICE by figuring out that the default JV Channel Verification parameter was key, I stumbled again.

I can send character data from end device to coordinator and see it come out of the UART.

But when I have an end device with the ADC enabled (ATD1=2) and the IO Sampling Rate (ATIR=4E20) set to 20 seconds, I see the coordinator's red LED flicker as data is being received. But nothing appears to come out of the coordinator's UART.

Back to drawing board with Mr Google. I searched websites, blogs, forums. Some are claiming it works great, others are having issues. I'm on the issue side.

Then came across this in the XBee ZNET 2.5 Product Manual, section 4.5.3. Periodic IO Sampling :
Periodic sampling allows an XBee / XBee-PRO module to take an IO sample and transmit it to a remote device at a periodic rate. The periodic sample rate is set by the IR command. If IR is set to 0, periodic sampling is disabled. For all other values of IR, data will be sampled after IR milliseconds have elapsed and transmitted to a remote device. The DH and DL commands determine the destination address of the IO samples. DH and DL can be set to 0 to transmit to the coordinator, or to the 64-bit address of the remote device (SH and SL). Only devices running API firmware can send IO data samples out their Uart. Devices running AT firmware will discard received IO data samples.
I swear that when I searched around, I found people claiming to receive IO data in AT mode. I surely can't so maybe the manual is on to something. I'll try API mode. Bet it's a boatload of fun.

TIP: When writing API firmware to a device that currently has AT firmware on it (or visa-versa), after the write, X-CTU complains that it failed to enter command mode. This is because the "Enable API" setting on the PC Settings / Host Setup tabs need to be toggled.

After putting ZNET 2.5 COORDINATOR API on, I did start to seem to receive data. But not what I expected. With AT Transparent Mode, I could just read a string straight from the UART and parse away. Now in API Mode I'm getting some weird gook that sure doesn't print very nice. Probably that API Frame thing that I skimmed past in the manual. Darnit, I gotta just sit down and read that thing.

Section 6 API Operations goes into detail about how life has now changed and the free ride is over. It says "API operation requires that communication with the module be done through a structured interface (data is communicated in frames in a defined order)". Oh Joy.

I started hacking my Python app to see what kinda data I was getting myself into. The frame starts with a start byte of 0x7E and then followed by a two byte length. The IO data that I'm trying to get to is locked away in the Frame Data block that doesn't seem too bad.

Luckily before I started to make a career out of writing a API Frame parsing mess, I went back to Google to see what the trail blazers have done for me. I found the xbee.py file that ladyada used for her tweet-a-watt. Too bad it's for Series 1 and not a lot of help. But the header notes the author as Amit Snyderman. Wonder if he has a Series 2 version. Woo Hoo, he's got some google code at http://code.google.com/p/python-xbee But wait. The page says: The XBee API code is incomplete, missing support for valid checksums and an implementation for working with the Series 2 API. Darn it. Another bad lead.

Well, I'm on the Python Xbee page, I might as well be nosey and poke around. Maybe I can use the Series 1 code as a starting point and hack it up enough to get my single channel ADC data. Let's look it the Subversion source. What's that under the trunk? An XBee 2.5 folder. Woo Hoo!

Grab the xbee_api.py file, save it to your project folder and then for a quick test, write a second file like this:
#!/usr/bin/env python

from xbee_api import *

# replace first param with your serial port
a=XbeeApi("/dev/tts/0",9600)

Save this in a file called coord.py, make it executable (chmod +x coord.py), and run it (./coord.py). Now when sampled IO data is received, this is printed. Do I hear a Woo Hoo?
onData() ->{'code': 146, 'data': {'asamples': [3, 255], 'dsamples': [[0, 0, 0, 0, 0, 0, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1]], 'dmask': [[0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0]], 'mac': [0, 19, 162, 0, 64, 62, 33, 116], 'samples': [1], 'address': [60, 237], 'amask': [[0, 0, 0, 0, 0, 0, 1, 1]], 'options': [1]}}
data {'asamples': [3, 255], 'dsamples': [[0, 0, 0, 0, 0, 0, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1]], 'dmask': [[0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0]], 'mac': [0, 19, 162, 0, 64, 62, 33, 116], 'samples': [1], 'address': [60, 237], 'amask': [[0, 0, 0, 0, 0, 0, 1, 1]], 'options': [1]}
Now I need to figure out to interpret the data 'asamples'. But this is looking very promising. FYI, I'm going down this path to hook a thermistor to an XBee for my pool thermometer. More details to follow as I make more progress.

More FYI. I'm running this with OpenWRT on a wl-520gu hacked for my tweet-a-w/e. The receiving XBee is named BLUE. The second XBee is name WHITE and is connected to my laptop with an adafruit XBee Adapter that has ADC pin 2 setup for 20 second periodic IO sampling.

I've saved the today's version of my X-CTU config files here:
BLUE with ZNET 2.5 COORDINATOR API

WHITE with ZNET 2.5 ROUTER / END DEVICE AT with ADC pin 1 enabled and 20 second IO sampling.

What do you think? Leave a comment.

XBee Series 2 - JV Channel Verification

Damn it. Why do the simplest things always take hours to complete? I'm using XBee Series 2 cause I really didn't know what I was buying. Series 1 or Series 2? To a newb, sounds like 2 is the shiny. Why buy an out-dated series 1? Nobody wants last year's model.

If I would've done my homework, the series 1 (also called 802.15.4) are the most reliable, easiest to use. So of course, I'm using Series 2.

I first attempted to use them as described in the ZNet 2.5 Modules Product Manual, one as a coordinator, one as an end device. No luck. I followed every instruction I could find on the web. Nada.

Just by dumb luck, I found that if I flashed two radios as ZNET 2.5 ROUTER / END DEVICE AT and then set the Destination Address of each to point to the other, it appeared to work. I could send data to and from the serial ports. I used them in this way for the tweet-a-w/e foundation and had good success.

For an end device called WHITE that will talk to another end device named PINK, use X-CTU and load the ZNET 2.5 Router / End Device AT firmware. Go to the X-CTU Terminal tab and type:
+++
ATRE
ATNIWHITE
ATDNPINK
ATWR
Here's the X-CTU file for WHITE.

But now I'm trying to send analog data from one to the other and I'm back to nada. The data receive light blinks and it appears to read data, but nada bit comes out of the UART.

I decided to go back to square one and reconfigure as coordinator and end device. Sounds easy? Right. Took me two nights to get the devices associated. What's associated? From page 14 of the 802.15.4 manual (and not in the series 2 manual):
Association is the establishment of membership between End Devices and a Coordinator. The establishment of membership is useful in scenarios that require a central unit (Coordinator) to relay messages to or gather data from several remote units (End Devices), assign channels or assign PAN ID.
The series 2 manual seems pretty decent. I've read many sections and need to sit down and attempt to read more. Too bad though that it doesn't mention parameter JV Channel Verification. Which is the parameter that finally made a difference.
[update] maybe I was looking at the wrong manual. The one I ref'd above is the Series 2 Manual. The JV parameter is mentioned in the ZNET 2.5 manual.

When setting up a coordinator and associated end devices, there's a few things that you need to do:
  • After writing the firmware, go to the terminal and do a ATRE to reset to factory values. I would've saved lots of time if I realized that previous values were not being overwritten between firmware loads.
  • Make sure that the PAN ID is set the same on both devices. I suggest that while debugging, you have both devices connected to your computer via an adafruit XBee Adapter or SparkFun XBee Explorer and use X-CTU to monitor. You need to verify that the Operating PAN IDs are the same when they're trying to associate.
  • Set each device to a unique Node ID
  • The coordinator destination address should be set to DH=0, DL=FFFF
  • The end device destination address should be set to DH=0, DL=0
And for basic communications, that should be it. No need to set destination address of the end device explicitly to the coordinator. The 0's do that for you.

My problem though was my coordinator was on channel F and my end device was channel D. I could see this while watching them start up, but I could not figure out WTF.

Finally by going thru every parameter on both devices using X-CTU, I realized that JV Channel Verification was the ticket. This is described in the X-CTU tool as:
Set/read the channel verification setting. If enabled, a router will verify a coordinator exists on the same channel after joining or power cycling to ensure it is operating on a valid channel, and will leave if a coordinator cannot be found (if NJ=0xFF). If disabled, the router will remain on the same channel through power cycles.
In English, this means that if your devices are on different channels and JV is disabled, you're fricked cause the router won't go to another channel to associate.

Change JV to enabled and reboot the device:

+++
ATJV1
ATWR
ATFR

I've uploaded the configuration to my coordinator named BLUE that has ZNET 2.5 COORDINATOR API firmware. And an end device named PINK that has ZNET 2.5 ROUTER / END DEVICE AT firmware.

What do you think? Leave a comment.

28 March 2009

XBee Adapter Kit PDQ

Just got done building an XBee Adapter and tested it in 34 minutes. That's right, 2040 seconds. Or 0.566666667 hours. Either way, it's PDQ.

I'm a soldering novice. I've been burning my finger tips for some years now but in limited batches so I'm in no way claiming any skill. (Actually, I'm claiming a lack of skill) And I wasn't speeding, I was in no rush to build this thing.

I opened the mail and snapped that there picture over there. Plugged in my soldering iron and started to look through the adapter pieces as it warmed up.

I built one of these last year so I knew the routine. They're so dirt simple, I didn't even have to look over adafruit's instructions this time. I just glanced at my other adapter to see which resistor was which. Everything else is a no-brainer to fit onto the board.

I did a few things different this time though. Since I don't have a PCB vice, I'm stuck with having the board laying flat on my bench. So I soldered in the 74125 buffer first, the female header sockets next, all the other pieces, and the male header pins last. Your mileage may vary, it was just simpler for me in that order.

So if I already have one of these (and a XBee Shield and a XBee Explorer), what the heck am I doing building another?

Now that I have a workable tweet-a-w/e, I need more XBees "whatevers".

I'm planning to have temperature sensor to tweet swimming pool data. So I need another XBee and an XBee Adapter.

I'm planning to use Sparkfun's Nike+ receiver in a Serial Adapter connected to an Arduino and start tweeting stalker data. So I need another XBee and an XBee Adapter.

I'm planning to hook up various ADC things to an Xbee and start tweeting real data. So I need another XBee and an XBee Adapter.

Get the point? I need more XBees and XBee adapter kits. Unfortunately the boss doesn't understand, so I will need to get these one or two at a time.

I finished soldering, slapped in an XBee and ran a test script that tweeted a tid bit to twitter. I knew I was making good progress but only noticed how fast after I snapped a second picture, and saw the timestamp. 34 minutes. I can't even make minute rice in 34 minutes.

22 March 2009

tweet-a-whatever (tweet-a-w/e) foundation

My explanation of the tweet-a-w/e ran pretty long in my last post. I took an Asus wl-520gu wireless router, installed OpenWrt, Python, and an Xbee to build the foundation of a tweet-a-whatever (tweet-a-w/e). My 13 year-old texting obsessed daughter choose that name BTW. The last post ran so long that I cut it off after I talked about how to read the serial port with pySerial.

This time, I'll explain a simple Python app that receives a string from a Xbee, decodes its header and then tweets the payload to a twitter account. I'm a complete Python newb so if you have suggestions on a better implementation, I'm all ears.

Here's basically what I've done. Heavily borrowed from mightyOhm and ladyada, this project receives data from Xbees in "the field". Using a unique header, the client app determines the payload destination which can be a tweet or a web service.

Mr mightyOhm has written a great series on hacking the Asus wl-520gu wireless router to build an open source wireless streaming internet radio receiver. If you haven't read thru Jeff's work, stop right now and read it. Now.

Ladyada's award winning tweet-a-watt takes data from a kill-a-watt, sends it to Xbee embedded in a hacked wl-520gu and then tweets it for the world to see how green you are. I've copied the Xbee connection from her and the idea of using Python. She didn't post her Python source, so I've had to stumble thru this. I just read tonite, that the tweet-a-watt project will be a project in Make Magazine, volume 18. [update] ladyada did post her source, I just missed it. Check out her good stuff here.

My tweet-a-w/e's spin is that any data generating thing (a "whatever") can be hooked to an Xbee in the field. Either directly to the Xbee or via an Arduino. The Xbee transmits to a receiving Xbee that has been wired into the serial port of a hacked Asus wl-520gu. The Python app running on the router reads the data and based on a unique header code, the payload is either tweeted or sent to a web service.

So I ended last time by showing a small snippet of Python that read the incoming data and printed it. Now let's modify that snip so it reads the first few characters and makes a decision of what to do. This first revision is pretty hackish and we'll improve on it as "whatevers" are added in the field. My opinion is get it working, then optimize.

Last time we installed Python but I didn't tell about the python-twitter wrapper from DeWitt Clinton. Here's what I did:
  • Install simple JSON dependency
  • wget http://pypi.python.org/packages/source/s/simplejson/simplejson-2.0.9.tar.gz
  • gunzip simplejson-2.0.9.tar.gz
  • tar xvf simplejson-2.0.9.tar
  • python setup.py install
And crash. Some weird error about "No module named _md5". WTFO?

Mr Google has no clue about this error. Plenty of problems, but I couldn't find anything. I mucked with my LD_LIBRARY_PATH and considered rebuilding Python. Finally, I actually started looking at the source referenced in the trace. Here's what I found. Install OpenSSL and problems are solved. I wrote about my fun here.
# opkg update
# opkg install openssl-util
And finally install python-twitter:
  • wget http://python-twitter.googlecode.com/files/python-twitter-0.5.tar.gz
  • gunzip python-twitter-0.5.tar.gz
  • tar xvf python-twitter-0.5.tar
  • python setup.py install
Woo hoo. We're ready to tweet.

I'm currently planning 4 whatevers that will be talking to the router:
Each whatever will be uniquely ID'd so the incoming data can be routed to a twitter acount or any other web resource. Because my originality lacks, I started with "[0]" for the test Xbee on my laptop, "[1]" for the temperature sensor, "[2]" for the Nike+, and "ox7e" for the Xbee.

I created an array to hold the whatever name, the unique header, the action to perform, and username/password:
# tweet-a-w/e stuff array
#   "whatever", header string, action, user, password
#
WE_ARRAY = [
['WE_TEST', '[0]', _tweet_it, 'username', 'password'],
['WE_TEMPERATURE', '[1]', _web_it, 'username2', 'password'],
['WE_NIKEPLUS', '[2]', _stalk_it, 'username3', 'password'],
['WE_XBEE', '0x7e', _tweet_it, 'username4', 'password']
]
The action array elements are functions to perform the tweet or web action. Here's what I defined:
#
# define functions to do something with received data
#

# tweet data
#
def _tweet_it(data, twuser, twpass):
# login to twitter
print 'tweet w/' + twuser
api = twitter.Api(username=twuser, password=twpass)
api.PostUpdate(data)

# web service data
#
def _web_it(data, twuser, twpass):
# tbd
print 'wrote to web service w/' + twuser

# stalk the data
#
def _stalk_it(data, twuser, twpass):
# tbd
print 'watch it w/' + twuser
Ok, now let's read the data and determine what we got:
# open up the serial port on router
#
print 'opening serial port ' + SERIALPORT
ser = serial.Serial(SERIALPORT, BAUDRATE, timeout=TIMEOUT)
ser.open()

# read data
#
try:
print 'entering read loop'

# loop forever
while 1:

# read it from serial port
data = ser.read(NUMCHARS)
if len(data) > 0:
print 'Read: ' + data

# loop thru our whatever array and see if we recognize the header
for i in range(0, len(WE_ARRAY)-1):
if data.startswith(WE_ARRAY[i][1]):
print WE_ARRAY[i][0] + " action " + WE_ARRAY[i][2].__name__
WE_ARRAY[i][2](data, WE_ARRAY[i][3], WE_ARRAY[i][4])
break

finally:
print 'closing serial port'
ser.close
The only tricky thing in that code is the action call. Once we have a header match, the 2nd array element is one of the action functions that we defined. Simply call it and pass the data, username/password as arguments.

My Python client file is here.

Get your file on the router and start it:
root@OpenWrt:/opt/project# python XbeeReader.py
And send it something. For me, a quick test is to use another Xbee in the SparkFun Xbee Explorer. When I plug this in to my laptop, it shows up as /dev/ttyUSB0 so to write a quick string is as simple as:
d@hopper:~/projects/wl-520gu$ echo "[0] Testing " + `date` > /dev/ttyUSB0
And on the router telnet window, I see:
opening serial port /dev/tts/0
entering read loop
Read: [0] Testing + Thu Mar 26 23:30:47 EDT 2009
WE_TEST action _tweet_it
tweet w/skibicki
I'm using a test twitter account and viola, the string "[0] Testing" shows up like magic.

How are the Xbees configured? Real easy. I put ZNET 2.5 Router / End Device AT firmware version 1244 on both. I use Linux so using X-CTU was a little tricky until I figured out how to use it with Wine. I wrote about it here.

The Xbee in the router is named PINK. Last year, I built the NKC Xbee Shield Kit and goofed something up. When I installed the Xbee, it got really hot and the white Xbee label turned pink. It still works but is discolored. Here's the X-CTU file for PINK.

The Xbee connected to my laptop via the Xbee Explorer is named WHITE. Because it's not discolored. The config is EXACTLY same as PINK except for the name and destination. After loading the ZNET 2.5 Router / End Device AT firmware, I went to the X-CTU Terminal tab and typed:
+++
ATNI WHITE
ATDN PINK
ATWR
Here's the X-CTU file for WHITE.

Plans for next time. Clean up the client so only the data is written to twitter and not the unique header. Then hook up the DS18B20 temperature sensor and start tweeting real data.

What do you think? Leave a comment.

21 March 2009

tweet-a-w/e using Asus wl-520gu and Xbee

Update: Newegg no longer stocks the Asus WL-520GU router but this is still a cool project. You can find these routers for $10-20 (or more) on eBay. Also someone sent me a comment that the Linksys WRT54G routers have serial ports that could be used in a similar manner. I haven't tried this yet but if a cheap Linksys router comes my way, I'll try and attempt to blog about it.
---------------------------------------------------------------------------------------------
A few of the geeks that I stalk follow on twitter have done some interesting things with the Asus wl-520gu wireless router. Jeff Keyzer of mightyOhm has a real nice series on using the router where he builds an open source wireless streaming internet radio receiver. He walks you step-by-step on why he chose the wl-520gu, how to load OpenWrt, adding a serial port header, and even interfacing it to an Atmel ATmega168 AVR microcontroller to drive an LCD. Very nice, thanks Jeff.

Mr mightyOhm is also the admin of the Asus Wireless Router Hacks Flickr Pool. Check out a few of the cool things he and others have done with the wl-520gu.

Not to be out done, LadyAda hacked her 520gu and ported her tweet-a-watt xBee code in a 5 hour hack fest. That's her router picture over on the right. Click it and see a brief description of her efforts. She mentions this project on twitter and on flicker but I haven't seen it on her blog. I've been messing with Xbees lately and ladyada's hack is just what i need.

I've been thinking about a tweet-a-temperature project that would monitor my pool temperature and tweet it every so often during the summer months. While I'm toiling away in the office, I could get SMS spam telling how great life is outside work.

Or maybe a tweet-a-lert that let's me know when my super-sekret door has been opened. Or a tweet-a-stalk when a victim with Nike+ shoes runs by a sensor.

Let's lay down the foundation for a tweet-a-whatever (tweet-a-w/e) project by hooking an Xbee to the router and installing a Python client that tweets whatever is received.

Yes, this seems to be very similar to ladyada's tweet-a-watt port. I'm shamelessly copying the hard work of mightyOhm and adafruit to set this up. What's my value-add? I'll ramble a lot and tell you about all my dead-ends.


We'll take a wl-520gu router, install openWrt firmware and configure it as a wireless client to my existing house wireless network. Using the 520gu's serial port, I'll hook up an Xbee in an Adafruit adapter. Next, install Python on an automount USB drive and write a client that reads incoming Xbee data and does the tweet.

So I ordered a wl-520gu from newegg and eventually pretty much followed mightyOhm's instruction for installing header pins on the serial port and installing OpenWrt.

If you've read any of my other ramblings on this blog, you know already that I'm not very original and sometimes not very smart. But I'm decent at stealing reusing other people's work and twisting it to fit my needs.

Let's first talk about the stupid things I did so you can avoid the mistakes in life that I have made.
  • Both mightyOhm and ladyada used OpenWrt by first flashing dd-wrt and then OpenWrt. Me? I choose to use tomato firmware instead because I wanted built-in USB support and the description of its feature sounded great. And because I wanted to waste 3 nights trying to configure it in wireless client mode. I'm not saying tomato is bad, I'm just saying that I struggled big time and I couldn't find many online tips. I finally said "uncle" and followed the mightyOhm's way and installed openWrt. And had it working in under a half-hour.
  • Installing a four-pin header isn't a hard thing. All I had to do was remove 4 blobs of solder and then solder in a header. So ... Why did I somehow brick my router? I was super duper careful removing the blobs (without a de-solder-er iron or a bulb) and then was careful soldering in the pins. I only applied heat for a max of 5 seconds and let it cool for about 5-10 seconds before re-attempting. Yet I did it. After I finished the header, the darn thing wouldn't boot. Nothing, dead to the world. No reset, no LED action, nada. I cussed for a day or two and then ordered a new one from newegg. Before I de-blobbed the new one, I ran down to Rat Shack and bought a desoldering bulb. 20 minutes later, I had a working router - with header pins. Lesson? If at first you don't succeed, spend more money.
  • With the default Asus firmware still installed, I found that logging in via telnet, the user/password was root/admin. Via the browser at 192.168.1.1:80, the user/password was admin/admin. Kinda odd, but maybe this tip will save you some hair.
Okay, so where are we in this story? Oh yeah, the start. Buy a wl-520gu router and boot it up fresh from the box. Don't muck with anything yet. Confirm that it starts and you can log in via a wired ethernet cable on a LAN port. The default address, username, password are on the bottom of the router (192.168.1.1, admin, admin).

On the 1st router that I bricked, I installed new firmware first, then tried to install the headers. On my working router, I did headers first, then firmware. I don't think it really matters what order you do this. But I ruined a $59 router, so you trusting me?

So do your own thing and follow mightyOhm's excellent instructions. Get headers on the serial port, install openWrt, and meet me back here in a few.

Back already? Good, you should have a router with OpenWrt that acts like a wireless client on your network. You should be able to wirelessly connect via telnet as well as via the serial port.

Yeah, I know the picture over there is kinda blurry but it's the best camera I have (donations accepted). If the picture was clear, you would see that I'm using a USB TTL-232 cable to talk to the router from a USB port to the router's shiny new headers. mightyOhm's picture on this page was very helpful for the pin-outs.

As is, the OpenWrt's filesystem is pretty cramped. There's not much space available to install extras such as Python. I had an extra Sandisk Cruzer laying around so I setup the router to automount the USB stick on /opt.

From either the serial line or a wireless telnet (my preference),
Then using hints from the openWrt wiki, I executed these commands:
  • # opkg update
  • # opkg install kmod-usb-core
  • # opkg install kmod-usb-ohci
  • # opkg install kmod-usb-storage
  • # opkg install kmod-fs-vfat
  • # opkg install kmod-fs-ext3
I installed both vfat and ext3 because my USB stick was currently fat but I wanted to reformat it to ext3. That should be pretty simple to do but as with most things, it was a hassle. I finally found how to do it and did this (click the link).

I want to install Python libraries and other extras on the USB drive so we'll need this auto-mounted at boot time. I couldn't locate clear instructions on this but here's what worked for me. Using tips from this place, I did:
  • Create a mount point
  • # mkdir /opt
  • Create an /etc/rc.d/S11mount file with macsat.com contents from this page.
  • NOTE: macsat's page references this as /etc/init.d/S11mount. For my version to work, I did not put it in init.d but instead put it in rc.d
  • # vi /etc/rc.d/S11mount
  • Paste in macsat's example S11mount content. My file version can be found here.
  • change the MOUNT_DEVICE0 statement to match your device. My USB stick was exactly the same as his example.
We're really making progress now. Next, we want to setup opkg so we can install large packages such as Python to the USB. Again, I'm using tidbit's from macsat.com excellent pages. Here's the good stuff.
  • Edit opkg config file to create an alternative destination for packages
  • # vi /etc/opkg.conf
  • Add this line:
  • dest opt /opt
  • My copy of the opkg.conf file is here.
Now some housekeeping. Since we want to install libraries and stuff on the USB stick, we'll need to continue following macsat's advice and update PATH and LD_LIBRARY_PATH in your /etc/profile. I set mine to:
  • export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/opt/bin:/opt/sbin:/opt/usr/bin:/opt/usr/sbin
  • export LD_LIBRARY_PATH=/lib:/usr/lib:/opt/usr/lib:/opt/lib
If you'll be installing services on /opt that you want to start at system boot time, be sure to check out macsat's script here. I'll be using this after I complete the client app. For now, I'm just manually starting.

At this point, we have a flashed wl-520gu router with OpenWrt, headers soldered to serial port, and configured to mount a USB stick to opt at bootup. Let's keep mushing on.

The line that we added to the top of our opkg.conf file
sets us up to install from the kamikaze 8.09 packages which contain the Python 2.5 that I seek. Do this to install Python on the opt mount:
  • # opkg update
  • # opkg -d opt install python
Assuming that you haven't fallen asleep and you've setup your route similar to mine, after a few minutes, Python and all dependencies will be safely setup on /opt. Give it a try:
  • # python -V
And you should see the proof:
Python 2.5.4
Using the AdaFruit Xbee Adapter that I blogged about last year, the next step is to simply hook it up to the 4-pin serial headers. This is the easiest step of all. 4 wires, hook 'em up.
  • wl-520gu GND -> Xbee GND
  • wl-520gu 3V -> Xbee 3V
  • wl-520gu TX -> Xbee RX
  • wl-520gu RX -> Xbee TX
Since OpenWrt uses the serial port /dev/tts for console login, we need to modify the /etc/inittab file. All this takes is to comment out the tts line:

#tts/0::askfirst:/bin/ash --login

I'm using Python for the client app, so we need to install the pySerial module from SourceForge. Create a temp folder on /opt, CD to it, download pySerial, unzip, untar, and install
All we need now is a app that reads the serial port and tweets. This blog getting pretty long so I'll just explain the serial port read and talk about the tweet part next time.

reader.py

#!/usr/bin/env python
import serial, time

SERIALPORT = "/dev/tts/0"     # the com/serial port the XBee is connected to
BAUDRATE = 9600               # the baud rate we talk to the xbee
TIMEOUT = 0.5                 # the timeout to wait for buffer fill
NUMCHARS = 140                # the number of characters to attempt to read at once

# open up the FTDI serial port to get data transmitted to xbee
print 'opening serial port ' + SERIALPORT
ser = serial.Serial(SERIALPORT, BAUDRATE, timeout=TIMEOUT)
ser.open()

try:
print 'entering read loop'
while 1 > 0:
data = ser.read(NUMCHARS)
if len(data) > 0:
print "Read " + data
finally:
print 'closing serial port'
ser.close
I've modified the reader.py quite a bit so that it tweets the received data, but the version above simply echoes the data to the terminal.

Run it like this:
# python reader.py
Send the Xbee data from another Xbee and watch it print.

Alright, I'm stopping and will pick up the story in my next entry. Look at this picture to see where we're going.



What do you think? Leave a comment.

Format USB Flash Drive with ext3

Using Ubuntu 8.04 Hardy Heron, I figured it would be easy to format a thumb drive to ext3. Seems for me that nutin is easy. My plans for the USB stick is to use it with an embedded Linux box that could expectantly shutdown.

Wikipedia says this about ext3:
The ext3 or third extended filesystem is a journaled file system that is commonly used by the Linux kernel. It is the default file system for many popular Linux distributions.
...
Its main advantage over ext2 is journaling which improves reliability and eliminates the need to check the file system after an unclean shutdown.
After a few struggles with attempting to use the Partition Editior, I ended up reformatting the stick by following these instructions:
  • mount your USB stick. With Ubuntu, most automount so this is as simple as sticking it in
  • do a "$ df" and determine your drive's partition name. Mine is /dev/sdb1
  • unmount the drive
  • Be very careful and double-dog sure that you have the right partition name.
  • Use your USB's partition name in-place of my example/dev/sdb1
  • Don't ruin your hard drive by using the wrong partition. Be careful !
  • $ sudo mkfs.ext3 /dev/sdb1
  • Give your drive a name
  • $ sudo e2label /dev/sdb1 usb-mydrive

What do you think? Leave a comment.

No module named _md5 - Missing OpenSSL?

I'm working a Python project on an embedded Linux device. As usual, I've had my struggles but yesterday I ran into one a tough one. I was afraid that I was going to have to dump Python and go to another language, ruining a week+ of effort.

I'm using OpenWrt kamikaze 8.09 and have been making excellent progress until I slammed into the wall when attempting to import md5. This spins off an odd trace that ends with "No module named _md5". It seems that in Python 2.5, the md5 module was deprecated for hashlib but something seems fishy.

I'm not the only dweeb with this problem. Mr Google has pages of people whining and tons of people talking about patches. Maybe they're not having the exact problem I did, but lots of problems and no one is reporting success.

Here's my exact issue:
# python
Python 2.5.4 (r254:67916, Feb 2 2009, 22:32:58)
[GCC 3.4.6 (OpenWrt-2.0)] on linux2
Type "help", "copyright", "credits" or "license" for more information.

>>> import md5
Traceback (most recent call last):
File "", line 1, in
File "/opt/usr/lib/python2.5/md5.py", line 6, in
from hashlib import md5
File "/opt/usr/lib/python2.5/hashlib.py", line 133, in
md5 = __get_builtin_constructor('md5')
File "/opt/usr/lib/python2.5/hashlib.py", line 60, in __get_builtin_constructor
import _md5
ImportError: No module named _md5
Using the various tips in the Google searches, I mucked with my LD_LIBRARY_PATH and considered rebuilding Python. Finally, I actually started looking at the source referenced in the trace. Let's see what's there.

/opt/usr/lib/python2.5/md5.py, line 6
0 # $Id: md5.py 39316 2005-08-21 18:45:59Z greg $
1 #
2 # Copyright (C) 2005 Gregory P. Smith (greg@electricrain.com)
3 # Licensed to PSF under a Contributor Agreement.
4
5 from hashlib import md5
6 new = md5
A simple import and new. Nothing exciting there. Next.

/opt/usr/lib/python2.5/hashlib.py, line 133
131 # lookup the C function to use directly for the named constructors
132 md5 = __get_builtin_constructor('md5')
133 sha1 = __get_builtin_constructor('sha1')
134 sha224 = __get_builtin_constructor('sha224')
Ok, we're calling a built-in contructor. I'm a complete Python newb. WTFO? Next.

/opt/usr/lib/python2.5/hashlib.py, line 60
54 def __get_builtin_constructor(name):
55 if name in ('SHA1', 'sha1'):
56 import _sha
57 return _sha.new
58 elif name in ('MD5', 'md5'):
59 import _md5
60 return _md5.new
Alright, here's where the built-in constructor is defined and sure enough, it imports _md5. What sleuthing, I'm amazing. We know that the whole frickin problem is that module _md5 can't be found, this tangent really helped.

Scratch head, ear, and other parts. Let's look at that code again. Why are we calling that built-in constructor? Actually, I have no clue. But I did notice line 126 that's conveniently located above our error at line 133.

126 except ImportError:
127 # We don't have the _hashlib OpenSSL module?
128 # use the built in legacy interfaces via a wrapper function
129 new = __py_new
130
131 # lookup the C function to use directly for the named constructors
132 md5 = __get_builtin_constructor('md5')

An ImportError exception. Yup, that's what we have. But a comment that mentions a missing OpenSSL module. Woo Hoo, eureka!

Frantically do this:
# opkg update
# opkg install openssl-util
And like magic, no more _md5 errors. For me, it was a simple missing dependency on OpenSSL. I hope your error is this easy to correct.

FYI, the top line of my opkg.conf file is:
BTW, opkg is a newer version of ipkg. Either is simply "the package manager on the system".

What do you think? Did this help you? Leave a comment.