Showing posts with label ubuntu. Show all posts
Showing posts with label ubuntu. Show all posts

Thursday, August 21, 2008

Automatically Partitioning GPS Logs with gpsbabel

My GPS logger is capturing lots of useful information but it's difficult to efficiently capture data for regular activities. Geotagging photos is easy, and manually working with the logs for a special event is possible, but it's not feasible to put in that much work to analyze commutes for example.

The logger creates a separate log file each time it's switched on and off, and while these logs could be sorted into categories for analysis, it's easy to forget to turn it on and off at the start and end of a section of interest and activities are then merged in the logs. In addition, there is often "junk" data at start and end of logs while leaving or arriving at a destination.

I wanted to be able to automatically capture the information about my daily activities by simply switching on the logger and carrying it around with me. I then simply want to plug the logger into the computer and have the logs automatically chopped into segments of interest that can be compared to each other over time.

The rest of this post roughly outlines the Python script I created to perform this task, minus some of the hopefully irrelevant details.

Firstly, I collect the lat/long coordinates of places that I am interested in collecting data while I'm there and traveling between them. These include my home, work, the climbing gym and so on. Each point has a radius within which any readings will be considered to be in that place.

#         id:  name lat         long        radius
places = { 1: ("A", -37.123456, 145.123456, 0.050),
           2: ("B", -37.234567, 145.234567, 0.050),
           3: ("C", -37.345678, 145.345678, 0.050) }
otherid = 4

For each of these places of interest, I then use gpsbabel's radius filter to find all the times where I was within that zone:

# create a list of all raw log files to be processed
from path import path
month = path("/gpslogs/200808")
logs = " ".join(["-i nmea -f %s"%log 
                 for log in sorted((month/"raw").files("GPS_*.log"))])

for (id,(place,lat,lon,radius)) in places.items():
   os.system("gpsbabel "
             # input files
             + logs
             # convert to waypoints
             + " -x transform,wpt=trk,del"
             # remove anything outside place of interest
             + (" -x radius,distance=%.3fK,lat=%.6f,lon=%.6f,nosort"%(radius,lat,lon))
             # convert back to tracks
             + " -x transform,trk=wpt,del"
             # output nmea to stdout
             + " -o nmea -F -"
             # filter to just GPRMC sentences
             + " | grep GPRMC"
             # output to log file
             + (" > %s/processed/place%d.log"%(month,id)))

And all points outside any of the specific places of interest are sent into an "other" file:

os.system("gpsbabel "
          # input files
          + logs
          # convert to waypoints
          + " -x transform,wpt=trk,del"
          # remove anything in a place of interest
          + "".join([" -x radius,distance=%.3fK,lat=%.6f,lon=%.6f,nosort,exclude"%(radius,lat,lon)
                     for (id,(place,lat,lon,radius)) in places.items()])
          # convert back to tracks
          + " -x transform,trk=wpt,del"
          # output nmea to stdout
          + " -o nmea -F -"
          # filter to just GPRMC sentences
          + " | grep GPRMC"
          # output to log file
          + (" > %s/processed/place%d.log" % (month, otherid)))

These files are filtered with grep to contain only minimal data as we only require the timestamps for this part of the process. Specifically only the NMEA GPRMC sentences are kept.

To provide a brief illustration, the following picture shows two log files of data, a blue and a green, between three points of interest:

The above process would create four files, one for each point A, B and C and one for "Other" points that would contain something like the following information, where the horizontal axis represents time:

I then read all those log files back in to create a "time line" that for each timestamp stores my "location" in the sense that it knows whether I was "home", at "work" or somewhere between the two.

# dict of timestamp (seconds since epoch, UTC) to placeid
where = {}
for placeid in places.keys()+[otherid,]:
   for line in (month/"processed"/("place%d.log"%placeid)).lines():
      fields = line.split(",")
      # convert date/time to seconds since epoch (UTC)
      t, d = fields[1], fields[-3]
      ts = calendar.timegm( (2000+int(d[4:6]), int(d[2:4]), int(d[0:2]),
                             int(t[0:2]), int(t[2:4]), int(t[4:6])) )
      where[ts] = placeid

This is then summarised from one value per second to a list of "segments" with a start and end time and a location. Unlogged time segments are also inserted at this point whenever there are no logged readings for 5 minutes or more.

# array of tuples (placeid, start, end, logged)
# placeid = 0 indicates "unknown location", i.e. unlogged
summary = []
current, start, stop, last_ts = 0, 0, 0, None
for ts in sorted(where.keys()):
   # detect and insert "gaps" if space between logged timestamps is greater than 5 minutes
   if last_ts and ts-last_ts > 5*60:
      if current:
         summary.append( [current, start, stop, True] )
      current, start, stop = where[ts], ts, ts
      summary.append( [0, last_ts, ts, False] )
 
   last_ts = ts

   if where[ts] != current:
      if current:
         summary.append( [current, start, stop, True] )
      current, start, stop = where[ts], ts, ts
   else:
      stop = ts
summary.append( [current, start, stop, True] )

(If there's a more "Pythonic" way of writing that kind of code, I'd be interested in knowing it.)

"Spurious" segments are then removed. These show up because when the logger is inside buildings the location jumps around and often out of the 50m radius meaning that, for example, there will be a sequence of Home-Other-Home-Other-Home logs. The "Other" segments that are between two known points of interest and less than 5 minutes long are deleted, as are "Other" segments that sit between a known place of interest and an unlogged segment.

Based on the above graphic, the summary might look something like the following:

startendlocation
10.00am10.05amA
10.05am10.30amOther
10.30am10.35amB
10.35am11.00amOther
...

The "Other" segments are then labelled if possible to indicate they were "commutes" between known locations:

startendlocation
10.00am10.05amA
10.05am10.30amA-B
10.30am10.35amB
10.35am11.00amB-C
...

Some segments cannot be labeled automatically and are left as "Other". This may be a trip out to a "one-off" location and back again, which can be left as "Other". However, sometimes it is because the logger didn't lock onto the satellites within the 50m radius on the way out of a place of interest and these can be manually fixed up later.

Once a list of "activities" has been obtained, with start and end times, it is easy to use gpsbabel again to split logs based on start and end of time segments:

for (place, start, stop, place_from, place_to, logged) in summary:
    dest = month / "processed" / ("%s-%s"%(time.strftime("%Y%m%d%H%M%S", time.localtime(start)),
                                           time.strftime("%Y%m%d%H%M%S", time.localtime(stop))))

   for (ext, chopFn) in [(".log", chopToLog),
                         (".kml", chopToKml), 
                         (".speed", chopToSpeedVsDistance), 
                         (".alt", chopToAltitudeVsDistance), 
                         (".hist", chopToSpeedHistogram),
                         (".head", chopToHeadingHistogram),
                         (".stops", chopToStopsVsDistance)]:
      if not (dest+ext).exists():
         chopFn(dest, locals())
         # make the file in case it was empty and not created
         (dest+ext).touch()

This generates a bunch of files for each segment, named with the start and end timestamps of the segment and an extension depending on the content. The first "chop" function generates an NMEA format log file that is then processed further by the remaining "chop" functions. The other chop functions will probably be explained in a later post, the first two are:

def chopToLog(dest, p):
    # filter input file entries within times of interest to temp file
    os.system("gpsbabel " + p["logs"]
              + (" -x track,merge,start=%s,stop=%s"
                 % (time.strftime("%Y%m%d%H%M%S", time.gmtime(p["start"])),
                    time.strftime("%Y%m%d%H%M%S", time.gmtime(p["stop"]))))
              + " -o nmea -F "+sh_escape(dest)+".log")

def chopToKml(dest, p):
    # create kml file with reduced resolution
    os.system("gpsbabel -i nmea -f "+sh_escape(dest)+".log"
              + " -x simplify,error=0.01k"
              + " -o kml -F "+sh_escape(dest)+".kml")

def sh_escape(p):
    return p.replace("(","\\(").replace(")","\\)").replace(" ","\\ ")

(Again, if there's a better way to handle escaping special characters in shell commands, I would like to know it.)

Using this, I can simply plug in the logger, which launches an autorun script, and the end result are nicely segmented log files that I can map and graph. More about that in another post.

Thursday, June 19, 2008

Using the Gosget GPS Data Logger in Linux via Wine (Ubuntu 8.04)

I managed to get the Gosget GPS Data Logger S1 to work under wine on Ubuntu 8.04 using (roughly) the following steps:

  1. Plug it into a usb port, Ubuntu will hot-plug it as a serial port automatically (probably /dev/ttyUSB0).
  2. Set the baud rate on the serial port:
    stty 115200 < /dev/ttyUSB0
    (I'm not sure if this is persistent after a reboot or not...)
  3. Create a "com" port in wine:
    ln -s /dev/ttyUSB0 ~/.wine/dosdevices/com1
  4. Put in the CD and run the installer in Wine:
    wine d:\PC\DataLogUtility\DataLogUtility.exe
  5. Run the Data Log Data Downloader via wine, put in the com port, connect, set a download folder and you'll be able to get .nmea files from the data logger.
  6. To convert the .nmea file to a .kml file I used gpsbabel -i nmea -o kml source.nmea dest.kml.

Figuring that out gave me a headache, hopefully this post will save someone else one. Please note however, that I'm writing the commands from memory so there may be something (hopefully harmless) wrong.

Also, if you switch the data logger into "G_Mouse" mode, it's possible to use gpsd to get GPS data in real-time. I switched the data-logger into "G_Mouse" mode via a Windows machine, but I presume the Data Log Data Downloader can do that via wine too.

And, gpsd and gpsbabel are both in the Ubuntu 8.04 repositories.

Sunday, May 18, 2008

Ambient Email Notifier in Linux

I finally got around to hooking up my Ambient Email Notifier under Ubuntu. No idea why I waited so long, given it pretty much worked as soon as I plugged it in!

The Linux kernel has drivers for the CP2102 USB-to-RS232 chip built in, so as soon as it was plugged in it showed up as /dev/ttyUSB0.

The Python-Serial module is in the Ubuntu repositories, so after installing that, I just changed my checkinbox.py script to use /dev/ttyUSB0 instead of COM3 and added a cron job to check every 10 minutes.

Ubuntu 8.04 + Logitech iTouch Keyboard = Dead Battery?

How is this possible? I've got a Logitech iTouch wireless keyboard that's been working fine for years now and with Ubuntu 7.10 for 5 months, but after upgrading to Ubuntu 8.04, my keyboard batteries have gone flat 4 or 5 times now. One or more of the batteries seems to "short out" to 0.1v or even go -ve. I've tried 2 sets of NiMH batteries, one brand new and one that had been working fine for years. Could it be anything other than a coincidence?

Update: After about 2 weeks of the batteries dying every day or so, I've now been on the same charge for about 2 weeks now. So, I've no idea what update fixed it, but it's better now!

Tuesday, May 06, 2008

Ubuntu 8.04 and NVidia TVOut

Upgrading to Ubuntu 8.04 has been fairly successful, but it took me a while to find out what had stopped my TV from working. Nothing seemed to be different, same xorg.conf file, no errors in /var/log/Xorg.0.log, "space" in the desktop for the TV, but the TV was black.

I finally found this nvnews forum post that gave the clue, and adding the following to the "Device" section in my xorg.conf file did the trick:

Option "TVOutFormat" "SVIDEO"
Option "TVStandard" "PAL-G"

Friday, February 29, 2008

abcde — A Better CD Encoder

ABCDE is a nice CD ripper/encoder utility for Linux.

It's all nice and automatic, and I managed to configure it to suit my mp3 management style pretty quickly. My .abcde.conf file:

OUTPUTDIR=/home/tom/Desktop
OUTPUTTYPE=mp3
LAMEOPTS='-h -b 224'
ACTIONS=default,playlist
OUTPUTFORMAT='${ARTISTFILE}/${ARTISTFILE} - ${TRACKFILE}'
VAOUTPUTFORMAT='Various/${ARTISTFILE} - ${TRACKFILE}'
PLAYLISTFORMAT='${ARTISTFILE}/${ARTISTFILE} - ${ALBUMFILE}.m3u'
VAPLAYLISTFORMAT='Various/Various - ${ALBUMFILE}.m3u'

That sets it up to rip to mp3s at 224kbs and write out a playlist file. The files are saved onto the desktop to test before I move them to my mp3 folder in the "Artist/Artist - Track.mp3" name format that I prefer.

mungefilename () {
 echo "$@" | sed s,:,\ --\ ,g | sed s,/,\ --\ ,g | tr \* + | tr -d \"\?\[:cntrl:\]
}

I also changed the filename processing so that colons and forward slashes are converted to double dashes (my scripts use single dashes to split the artist from the track title.) And I don't convert spaces to underscores and I leave single quotes alone.

MAXPROCS=2

Runs an encoder process on each core so it goes super fast.

I run it from an icon on the desktop with the following command line:

gnome-terminal -x abcde

Update: I just discovered the "gnome-terminal -x" is unnecessary, there's an option to launch an application in a terminal window automatically...

Friday, January 18, 2008

Cover-art Wallpaper under Ubuntu

This is an update to the previous post to create a cover art "stack" for Windows XP wallpaper to generate and update the wallpaper in Ubuntu.

Not much had to change, except the setWallpaper function:

def setWallpaper( bmp ):
    os.system( "gconftool-2 -t string -s /desktop/gnome/background/picture_filename %s" % bmp.replace(" ","\\ ") )

Ubuntu File System Setup

The following is a rough summary of how I've setup my Ubuntu file system, including some links to resources I found useful.

I have 3 hard-drives, a 500gb drive for the system and "non-critical" files, and two 300gb drives to hold "valuable" data. "Valuable" data includes documents, photos, source-code, music and some videos, less valuable "non-critical" data includes recorded TV programs, movies and so on.

On my Windows XP system I was using a RAID-1 setup to automatically mirror my data drive to protect against hard-drive failure. Using a RAID-1 array keeps everything nice and simple, but under Ubuntu I decided that the tools were more powerful and I would be able to "manually" mirror the data to gain a little more control. In particular, while I want a copy of the data in case of hard-drive failure, a slightly delayed backup in case of catastrophic user error would also be very valuable.

So, each drive is mounted separately, and the 500gb drive is partitioned to give some space for the system and the majority for "stuff" (videos, backups etc.)

I highly recommend partitioning the drives before/during installation, as while it was possible to move everything around, it was time consuming and fiddly. I used a Gparted LiveCD to split my main drive to get a separate /stuff partition, but this took forever (about 15 hours, and my system isn't slow...). I used the GParted LiveCD because this tutorial on installing WinXP on a Linux system suggested that the version of GParted on the Ubuntu LiveCD wouldn't work properly.

This guide on moving /home to a separate partition was helpful, though I think I just used cp -a to copy the files, rather than the complicated find | cpio command..

Once I had it all up and mounted, I created a script to run rsync to mirror the drives:

#!/bin/bash
date >> /home/tom/backup_mirror_home.log
rsync --archive --delete --stats /home/ /homeMIRROR/ 2>&1 >> /home/tom/backup_mirror_home.log

This was added in as a cron job to run every 3 hours, which should be often enough (sudo crontab -e):

# m h  dom mon dow   command
0 */3 * * * /home/tom/backup_mirror_home.sh

The rsync process took a while to copy the 200gb or so across the first time, but is very fast (probably less than a minute) when just synchronizing the existing files as most data on the drive is photos and music that don't change very often.

A useful trick to keep an eye on the progress of the copy operations is watch df. df shows the current usage on the various disks, and watch will run it every 2 seconds so you can watch the percentages change slowly and be convinced something is actually happening.

Once all that was done, I setup sbackup to backup to the /stuff drive and an external USB drive.

My actual process was a little more complicated than described above, as the 300gb drives already contained NTFS partitions with the critical data on them and I wanted to move it over to ext3 while at no point having less than 2 copies of the data. The steps were roughly as follows:

  1. Mount ntfs drive (/dev/sdb), copy data to 500gb drive (/dev/sda)
  2. Format /dev/sdb as ext3, copy data from /dev/sda to /dev/sdb.
  3. Move /home to /dev/sdb, and reorganise it a bit (rename/move folders/files about.)
  4. Format /dev/sdc as ext3, mirror from /dev/sdb

Tuesday, December 04, 2007

Installing Ubuntu was easy!

I just got myself a new computer and started running Ubuntu on it, I'm astonished at how easy the whole process of installation was. Over the years I've installed Windows countless times, always a painful process, though I haven't installed Vista so maybe they finally figured it out.

So, I built the computer, burnt an Ubuntu live CD, put it in and the first suprise was that I hadn't stuffed anything up putting the machine together. The live CD is really functional, and I was able to start up Pidgin, browse the web and most usefully, remote desktop into my old PC.

I only have one keyboard/mouse and monitor and two machines, both of which I want to continue to use for a while as I'm expecting that transitioning from my old system to the new one will take some time as the machine is used for development, as a full media center as well as web browsing etc. and I don't know how long it will take to learn how to do all that under Linux. Being able to remote desktop back to the old machine solves the problem nicely.

After playing with Ubuntu a bit and chatting with friends for a while I clicked "Install". A bit of deliberation was required to determine the partition layout I want, as in the future I expect I will install XP and dual boot. Finding an article on installing XP after Linux set my mind at ease so I figured I'd be able to repartition as necessary later. So I chose all the default install options and that plugged away in the background for a little while.

The truly impressive bit was that while this was happening, I was able to continue chatting with friends to get advice on installation options, search the web for answers and so on. It only required a quick restart and I was all done, and I didn't really stop "working" the whole time.

I did hit two small snags:

  • During the restart I realized I'd "boxed" in the CD drive so had to hastily hit the power switch, pull of the front bezel and open a slot so it could eject... my bad :)
  • Installing Opera didn't go very smoothly, I tried using Add/Remove programs but it failed mysteriously with the following error:
    Downloading the .deb file from the Opera website failed the first time too, but when I ran it again it worked, so there may be something broken with the way the dependencies went on and once they'd be put on by previous attempts it succeeded.

I guess I probably won't have to reinstall Ubuntu as often as I did Windows, but at least I won't dread the process.