Early last year I created a
twitter account. Not really sure why I did. I guess no one wants to be left behind in the micro blogging world. Other than announcing that I wrote another entry on this blog, I never did anything with twitter.
Later, I stumbled on
JTwitter which is a small Java library to the Twitter API. It lets you set/get your Twitter status, manage your network of friends, message friends, etc. I goofed with it for a few minutes. Example usage:
// Make a Twitter object
Twitter twitter = new Twitter("my-name","my-password");
// Print Daniel Winterstein's status
System.out.println(twitter.getStatus("winterstein"));
// Set my status
twitter.updateStatus("Messing about in Java");
Interesting. But I have no app in mind for Twitter, so JTwitter was
bookmarked on Delicious for later. Today I saw on
Make a cool project where a
washing machine sends a message to Twitter when it's done. Pretty neat but if I hacked into my wife's LG, I'd be dead meat.
Because of Arduinos and Xbees, I've been spending lots of time on
ladyada's site either buying stuff at adafruit or trying to get smarter by lurking in the
forums. Ladyada seems to be alright but she has
weird hair. She's pretty quick on answering forum questions and builds decent stuff. I really like her
Xbee Adapter Kit.
Chris Anderson is an editor for Wired magazine. He also has a website for DIY UAVs at
http://diydrones.com. Last year, I spent lots of time at his site 'cause that's something else that I want to build some day. After I read that Washing Machine Twitter thing, I read a blurb about
Chris' new business model for an open source robotics startup. He says:
I'm ok with that business model but the reason I'm spinning this long story is that Chris spilled those beans via Twitter. Oh, I didn't know
he had a Twitter account. I wonder what else he writes about? He writes about life and stuff. I wonder who he follows on twitter? He's the editor for Wired mag, probably follows some interesting people, huh? Sure enough, Tim O'Reilly, Phillip Torrone (Make mag), Brad Stone (NY Times), Steven Levy (found Einstein's brain, wrote Hackers, etc), and others. And Ladyada.
Ladyada's twitter status hadn't been updated in quite awhile but it was filled with entries such as:
That looked interesting as I recognized the entries as GPS strings. The National Marine Electronics Association (NMEA) has developed a specification that defines a GPS interface for the various hardware devices. NMEA also has its own version of essential GPS pvt (position, velocity, time) data. It is called RMC, the Recommended Minimum. These strings are prefaced with $GPRMC. Read all about it here.
Adafruit has a Botanicalls Twitter kit that allows plants to ask for human help via a Twitter message like "water me please" and "you over watered me". But plants don't have GPS, so I'm guessing that ladayada was messing with her Arduino GPS Shield and was logging position updates to Twitter.
I wonder where that pvt data points to? Using JTwitter, I grabbed ladayada's last status:
// Make a Twitter object
Twitter twitter = new Twitter("twitter-username","twitter-password");
// Print the lady's status
System.out.println(twitter.getStatus("ladyada"));
As expected, out pops:
$GPRMC,045519.000,A,4042.5917,N,0 7400.5417,W,0.68,290.51,160808,,* 17
In that string, the latitude is 4042.5917N. The longitude is 7400.5417W. Where is the hell is that?
Another Google search led me to a an interesting article on Sun's website, Working with Bluetooth and GPS. I grabbed the source code and parsed ladayada's string:
gps.datatypes.Record record = new gps.datatypes.Record();
System.out.println("recordtype="+
gps.parser.Parser.parse(twitter.getStatus("ladyada").getText(), record));
System.out.println("recordlat="+record.lattitude);
System.out.println("recordlon="+record.longitude);
And out pops:
recordlat=40.70986
recordlon=-74.009026
Ok, that's more like it. Somewhere in the good 'ol USA, NorthEast area. Stuff it into Google maps and sure enough, ladyada was stomping around in New York City.
What can I do with this new knowledge? I still have no app in mind but I have a few more tools to think about. And stick 'em in the shed for later use. That old shed is getting pretty full of tools for later. I better start oiling 'em before they all get rusty.
The src that I ref'd above needed some tweaking to convert the lat/lon strings to decimal degrees. I found another spot for the same code but was slightly diff in the parsing for GPRMC: Simple J2ME NMEA parser. Neither Parser was just what I needed, so I combined the two into the method below. This code is owned by Dominik Schmidt, I just modified it some.
/**
* NMEA-0183 Parser. Parses data sent by GPS receiver. As data is being
* transfered via XML to server, parsing consists in most cases of separating
* fields.
*
* @author Dominik Schmidt
*
* Copyright (C) 2006 Media Informatics Group (http://www.mimuc.de),
* University of Munich, Contact person: Enrico Rukzio
* (Enrico.Rukzio@ifi.lmu.de)
**/
public static int parse(String s, Record record)
throws UnsupportedTypeException, ParseException {
// Tokenizer to separate tokens
Tokenizer tokenizer = new Tokenizer(s);
// Type of record
int type;
try {
String token = tokenizer.next();
if (token.equals("$GPRMC")) {
type = TYPE_GPRMC;
token = tokenizer.next();
record.dateTimeOfFix = token.substring(0, 2) + ":"
+ token.substring(2, 4) + ":" + token.substring(4, 6);
record.warning = tokenizer.next().equals(Record.WARNING);
// orig version just stored lat/lon strings
// record.lattitude = tokenizer.next();
// record.lattitudeDirection = tokenizer.next();
// record.longitude = tokenizer.next();
// record.longitudeDirection = tokenizer.next();
// I used a pieces from second src to convert lat/long to decimal degrees
// Latitude
String raw_lat = tokenizer.next();
String lat_deg = raw_lat.substring(0, 2);
String lat_min1 = raw_lat.substring(2, 4);
String lat_min2 = raw_lat.substring(5);
String lat_min3 = "0." + lat_min1 + lat_min2;
float lat_dec = Float.parseFloat(lat_min3)/.6f;
float lat_val = Float.parseFloat(lat_deg) + lat_dec;
// Latitude direction
record.lattitudeDirection = tokenizer.next();
if(record.lattitudeDirection.equals("N")){
// do nothing
} else {
lat_val = lat_val * -1;
}
record.lattitude = lat_val + "";
// Longitude
String raw_lon = tokenizer.next();
String lon_deg = raw_lon.substring(0, 3);
String lon_min1 = raw_lon.substring(3, 5);
String lon_min2 = raw_lon.substring(6);
String lon_min3 = "0." + lon_min1 + lon_min2;
float lon_dec = Float.parseFloat(lon_min3)/.6f;
float lon_val = Float.parseFloat(lon_deg) + lon_dec;
// Longitude direction
record.longitudeDirection = tokenizer.next();
if(record.longitudeDirection.equals("E")){
// do nothing
} else {
lon_val = lon_val * -1;
}
record.longitude = lon_val + "";
record.groundSpeed = tokenizer.next();
record.courseMadeGood = tokenizer.next();
token = tokenizer.next();
record.dateTimeOfFix += "/" + token.substring(0, 2) + "."
+ token.substring(2, 4) + "." + token.substring(4, 6);
record.magneticVariation = tokenizer.next();
} else if (token.equals("$GPGGA")) {
type = TYPE_GPGGA;
// Time of fix
tokenizer.next();
// Lattitude
tokenizer.next();
// Lattitude direction
tokenizer.next();
// Longitude
tokenizer.next();
// Longitude direction
tokenizer.next();
record.quality = tokenizer.next();
record.satelliteCount = tokenizer.next();
// Ignore rest
}
// Type is not supported.
else {
throw new UnsupportedTypeException("Type " + token
+ " is not supported.");
}
}
// Parsing exception.
catch (NoSuchElementException e) {
throw new ParseException("Unexpected end of input.");
}
return type;
}