Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Where Am I?

0.00/5 (No votes)
15 Feb 2009 2  
A Windows Mobile app for location tracking in a route without GPS and internet.
Wami Screenshot

Introduction

Where am I (Wami) is a Windows Mobile application suite that tells you where you are in the middle of a trip, without GPS and without connecting to the internet, using cell broadcast and cell tower information and a pre-recorded route file instead. It works by recording cell broadcast and tower information along a route into a route file and then using the current cell broadcast/tower information to index into the route file and find out the relative location within the route.

Motivation

I regularly travel to my hometown and I usually take the night train, preferring to sleep during the 9-10 hour journey. If I woke up during the journey, I had no way of knowing where I was and how long it would take to reach my hometown - I'd have to wait for the next major junction to find that out. Wami was written to solve that problem. With my train route recorded, I can now know my location and the estimated time of arrival at my destination with just a glance at my mobile phone. Along the way, I also added the ability to automatically respond to SMSes from configured numbers with the location and ETA information, and the ability to sound an alarm when I'm close to the destination.

Using Wami 

The Wami suite has three executables.

  1. RouteLogger - Runs on the phone and records all cell broadcast and tower information into a route file. Closing the app saves the recorded route into a location that will be probed by Wami.

    Route Logger Screenshot

  2. Wami - Main application which reads in the route file recorded by RouteLogger and shows you the current location and ETA at destination. When launched, Wami presents you with a list of route files recorded by Route Logger (from Application Data\wami\Routes) from which you can choose the appropriate route. Wami then loads the route file and shows the following screen:

    Wami UI description

  3. RouteEditor - Desktop application that lets you edit route files. The best way to edit the route files generated by RouteLogger is to:
    1. Clear All existing location groups.
    2. Add new location group, with a meaningful name.
    3. Expand the range of cell towers covered by the location group, by clicking on Expand Right (Alt+r) or Expand Left (Alt+e).
    4. Keep adding new location groups, until you have covered all locations.
    5. Save the modifications.

Route Editor Screenshot

If you are taking a trip for the first time, launch RouteLogger and keep it running until you reach your destination. RouteLogger uses text that is broadcast by your cellphone provider to associate a name to a cell tower, and that might not always be accurate or useful. RouteEditor lets you edit the route file to rename and group locations into more meaningful location groups.

Next time onwards, launch Wami and load the appropriate route file, that's all there is to it. You don't even need to have cell broadcast turned on for Wami to work, it can use the cell tower information in conjunction with the information in the route file to give you location names.

How It Works

The heart of the Wami suite is wamilib.dll, an assembly that houses all the core data structures and classes in the suite.

Click to enlarge image

LocationChangeNotifier

This class triggers location change events, with the current cell broadcast text and the cell tower id values as event parameters. It does this by:

  • Subscribing to the Changed event of the PhoneCellBroadcast system state.
    cellBroadcastChanged = new SystemState(SystemProperty.PhoneCellBroadcast, true);
    cellBroadcastChanged.Changed += 
    	new ChangeEventHandler(cellBroadcastChanged_Changed);
  • Running a timer that periodically gets cell tower information from the Radio Interface Layer (RIL). There is no managed interface to the RIL, no nice event that we can subscribe to. We'll have to P/Invoke the functions ourselves, and run a timer to know if the cell tower changed.
    private string GetCellTowerId()
    {
       IntPtr handleToRIL = IntPtr.Zero;
       try
       {
          var result = RIL_Initialize(1, new RILRESULTCALLBACK(RILResultCallback), 
    			null, 0, 0, out handleToRIL);
    
          if (result != IntPtr.Zero || handleToRIL == IntPtr.Zero)
             return "";
    
          result = RIL_GetCellTowerInfo(handleToRIL);
    
          // If the RIL call succeeds, wait for the result, otherwise don't bother.
           if (result.ToInt32() >= 0)
           {
               cellTowerDataReturned.WaitOne();
           }
    
           return currentCellId;
           }
       finally
       {
          if (handleToRIL != IntPtr.Zero)
          {
              RIL_Deinitialize(handleToRIL);
          }
       }
    }

    The code is this long because the RIL_GetCellTowerInfo method is asynchronous - the OS calls back using the RILResultCallback delegate passed to it in the RIL_Initialize method. I tried to optimize this by calling RIL_Initialize only once and storing the returned handle, but that caused weird issues like the callback occasionally not running and causing an infinite wait on the WaitOne call.

RouteTracker

RouteTracker subscribes to the raw notifications from LocationChangeNotifier and translates it into a higher level LocationChanged event, using the information from the route file to look up Location objects for the given cell broadcast text and tower ids. It also keeps track of the current location along the route.

private void ProcessCellLocationChange(string newLocationName, string cellTowerId)
{
    newLocationName = newLocationName.Trim();
    currentLocationName = newLocationName;

    if (currentRoute != null)
    {
        Location location = 
		GetLocationForNameAndCellTowerId(newLocationName, cellTowerId);
        if (location != null)
        {
            ProcessKnownLocation(location);
        }
        else
        {
            ProcessUnknownLocation(newLocationName);
        }
    }
}

RouteManager, RoutePoint and Route

RouteManager acts as a single point interface for loading and saving routes. A Route is a collection of RoutePoints, with each route point representing a distinct location along the route. The RoutePoint also carries the time taken to reach the current route point from the previous route point, so finding the time to destination becomes the simple matter of adding all such time spans from the current point to the destination point.

Location and LocationGroups

A LocationGroup is simply a bunch of locations grouped under a recognizable name. For e.g. locations like San Francisco, Fremont and Sacramento can be grouped under California. LocationGroups exist because any decent city, at least in India, has multiple cell towers. Those towers will obviously have different cell ids/broadcast text, and it helps to organize them into a single identifiable entity, say Chennai.

Auto SMS Response

The Auto SMS response feature lets people chosen by you know your whereabouts. The Configure Auto SMS allows you to add contacts and specify a specific message. Wami will respond with your location and ETA to destination only if an incoming SMS message is from one of your chosen contacts and the message text matches the configured message. The SMSHandler class handles response to SMS requests. It uses the MessageInterceptor class from the Compact Framework to listen for incoming messages with the same body text as the configured message.

private void InitializeInterceptor()
{
   if (interceptor == null)
   {
      interceptor = new MessageInterceptor(InterceptionAction.NotifyAndDelete, true);
   }
   else
   {
      interceptor.Dispose();
      interceptor = new MessageInterceptor(InterceptionAction.NotifyAndDelete, true);
   }

   if (currentConfiguredMessage != null)
   {
      interceptor.MessageCondition = new MessageCondition
		(MessageProperty.Body, MessagePropertyComparisonType.Equal,
                        currentConfiguredMessage, false)
   }

   if (interceptor != null)
      interceptor.MessageReceived += new MessageInterceptorEventHandler
				(interceptor_MessageReceived);
}

The MessageInterceptor unfortunately does not provide a way to look for specific contacts, so that's handled by NotificationManager. NotificationManager maintains a list of triggers and the corresponding notifications - receiving an SMS with the configured body text is a trigger, and that trigger causes it to execute the corresponding notification action, i.e. replying to the SMS with the current tracking information.

private void ProcessTrigger(NotificationTrigger trigger)
{
   List<string> usersToNotify = new List<string>();

   foreach (var pair in userPredicateMap)
   {
      if (pair.Value(trigger))
      {
         usersToNotify.Add(pair.Key);
      }
   }

   if (usersToNotify.Count == 0)
      return;

    var message = GetMessage();

    if (message != null)
       notifier.Notify(usersToNotify.ToArray(), message);
}

Location Based Alarm

The CustomSoundPlayer class is used to play sound files on the device. It P/Invokes methods on aygshell.dll to asynchronously start and stop playing sounds.

public static class CustomSoundPlayer
{
        static IntPtr soundHandle;
        static object lockObject = new object();

        public static event EventHandler<eventargs> PlayingSound;
        public static event EventHandler<eventargs> StoppedPlaying;

        public static void PlaySound(string path)
        {
            lock (lockObject)
            {
                if (soundHandle != IntPtr.Zero)
                    return;

                SndOpen(path, ref soundHandle);
                SndPlayAsync(soundHandle, 0x1);
            }

            if (PlayingSound != null)
                PlayingSound(new object(), new EventArgs());
        }

        // Not threadsafe
        public static void StopPlaying()
        {
            lock (lockObject)
            {
                if (soundHandle == IntPtr.Zero)
                    return;

                SndStop((int)SndScope.Process, IntPtr.Zero);

                SndClose(soundHandle);
                soundHandle = IntPtr.Zero;
            }

            if (StoppedPlaying != null)
                StoppedPlaying(new object(), new EventArgs());
        }

       [DllImport("aygshell.dll")]
       internal static extern uint SndOpen(string file, ref IntPtr phSound);

       [DllImport("aygshell.dll")]
       internal static extern uint SndPlayAsync(IntPtr hSound, uint flags);

       [DllImport("aygshell.dll")]
       internal static extern uint SndStop(int soundScope, IntPtr hSound);

       [DllImport("aygshell.dll")]
       internal static extern uint SndClose(IntPtr hSound);
}</eventargs></eventargs>

The LocationAndETAWatcher class subscribes to LocationChanged and TimeChanged methods of RouteTracker and provides the ability to execute single shot actions when the appropriate location/time change occurs. The location based alarm feature works by hooking up CustomSoundPlayer's PlaySound to LocationAndETAWatcher. The user can configure Wami to sound an alarm if:

  • Time to destination becomes lesser than a configured value.
  • Current location group becomes equal to a configured value.

LocationAndETAWatcher can watch both location changes and ETA changes, so the UI simply creates appropriate predicates for the above two conditions and sets up LocationAndETAWatcher to execute CustomSoundPlayer's PlaySound method.

watcher.Initialize(routeTracker);

if (!string.IsNullOrEmpty(s.SoundPlayLocationGroupName))
{
   watcher.AddSingleShotActionForLocationChange
	(locationGroup => locationGroup.Name == s.SoundPlayLocationGroupName,
           () => PlaySound(s.SoundFilePath));
}

var timeSpanToDestinationConfiguration = s.TimeSpanToDestination;
if (timeSpanToDestinationConfiguration != TimeSpan.Zero)
{
   watcher.AddSingleShotActionForETA
	(liveTimeSpanToDestination => liveTimeSpanToDestination <= 
           timeSpanToDestinationConfiguration, () => PlaySound(s.SoundFilePath));
}

Challenges

I have to say that writing a UI application for mobile devices is much more difficult than writing one for a desktop. Besides the obvious limitation in space and availability of controls, there's also orientation and wildly different aspect ratios to consider. Not something I enjoyed doing. The other hard thing is unit testing of the code. Without mocking, I'd be broke by now, having sent thousands of SMS messages from my phone to test the Auto SMS response feature:). I mocked everything I could, trying to avoid finding bugs when running on the phone. Cellular Emulator helped a great deal too.

References

History

  • 3:48 PM 1/14/2009 - Initial submission
  • 11:26 AM 1/15/2009 - Updated references and resized class diagram to avoid horizontal scrolling

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here