Thursday, February 7, 2008

Integrating with Garmin Mobile XT

Half a year ago, I wrote an article about Integrating with TomTom Navigator. This time I'm gonna discuss how you can integrate a .NET Compact Framework application with the Garmin Mobile XT navigation software. The process is a bit similar to integrating with TomTom because the Garmin SDK only provides a native API.

Before we get started, we need to have the Garmin Mobile XT for the Windows Mobile platform. Unlike TomTom's SDK, Garmin's SDK is available free of charge for download.

Before we can get more into detail, we will need the following:
1) Visual Studio 2005 or 2008
2) Windows Mobile 5.0 SDK for Pocket PC
3) A windows mobile device a GPS receiver and the Garmin Mobile XT (and Maps)
4) Garmin Mobile XT SDK for the Windows Mobile platform

We will be making the same projects we made for Integrating with TomTom Navigator:
1) Native wrapper for the Garmin XT SDK
2) Managed Garmin XT SDK wrapper
3) Device application that will call the Garmin XT SDK wrapper methods


Let's get started...


Native wrapper for the Garmin XT SDK

The Garmin SDK ships with C++ header files and a static library that a native application can link to. For that reason we need to create a native DLL that exposes the methods that we need as C type funtions. Let's call this Garmin.Native.dll.

In this article, we will implement a managed call to the Garmin Mobile XT to allow us to launch the Garmin Mobile XT, Navigate to a specific address or GPS coordinate, and to Show an address on the Map. These tasks will be performed on a native wrapper and which will be called from managed code.

We will be using the following methods from the Garmin Mobile XT SDK:
- QueLaunchApp
- QueAPIOpen
- QueAPIClose
- QueCreatePointFromAddress
- QueCreatePoint
- QueRouteToPoint
- QueViewPointOnMap

These methods return specific error codes describing whether the command executed successfully or not. This error information is translated to a .NET Framework enum which we will see later.


#include "QueAPI.h"

#define EXPORTC extern "C" __declspec(dllexport)

long DecimalDegreesToSemicircles(double degrees);

BOOL APIENTRY DllMain(
  HANDLE hModule,
  DWORD ul_reason_for_call,
  LPVOID lpReserved)
{
  switch (ul_reason_for_call)
  {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
    break;
  }
  return TRUE;
}

static void QueCallback(QueNotificationT8 aNotification)
{
  // Used for debugging purposes
}

EXPORTC int CloseAPI()
{
  QueErrT16 err = QueAPIClose(QueCallback);
  return err;
}

EXPORTC int OpenNavigator()
{
  QueErrT16 err = QueLaunchApp(queAppMap);
  return err;
}

EXPORTC int NavigateToAddress(
  const wchar_t *streetAddress,
  const wchar_t *city,
  const wchar_t *postalCode,
  const wchar_t *state,
  const wchar_t *country)
{
  QueErrT16 err = QueAPIOpen(QueCallback);
  if (err != gpsErrNone) {
    return err;
  }

  QueSelectAddressType address;
  QuePointHandle point = queInvalidPointHandle;

  memset(&address, 0, sizeof(QueSelectAddressType));
  address.streetAddress = streetAddress;
  address.city = city;
  address.postalCode = postalCode;
  address.state = state;
  address.country = country;

  err = QueCreatePointFromAddress(&address, &point);
  if (err == queErrNone && point != queInvalidPointHandle) {
    err = QueRouteToPoint(point);
  }

  QueAPIClose(QueCallback);
  return err;
}

long DecimalDegreesToSemicircles(double degrees)
{
  return degrees * (0x80000000 / 180);
}

EXPORTC int NavigateToCoordinates(double latitude, double longitude)
{
  QueErrT16 err = QueAPIOpen(QueCallback);
  if (err != gpsErrNone) {
    return err;
  }

  QuePointType point;
  QuePositionDataType position;

  memset(&position, 0, sizeof(QuePositionDataType));
  position.lat = DecimalDegreesToSemicircles(latitude);
  position.lon = DecimalDegreesToSemicircles(longitude);

  memset(&point, 0, sizeof(QuePointType));
  point.posn = position;

  QuePointHandle hPoint;
  memset(&hPoint, 0, sizeof(QuePointHandle));

  err = QueCreatePoint(&point, &hPoint);
  if (err == queErrNone && hPoint != queInvalidPointHandle) {
    err = QueRouteToPoint(hPoint);
  }

  QueAPIClose(QueCallback);
  return err;
}

EXPORTC int ShowAddressOnMap(
  const wchar_t *streetAddress,
  const wchar_t *city,
  const wchar_t *postalCode,
  const wchar_t *state,
  const wchar_t *country)
{
  QueErrT16 err = QueAPIOpen(QueCallback);
  if (err != gpsErrNone) {
    return err;
  }

  QueSelectAddressType address;
  QuePointHandle point = queInvalidPointHandle;

  memset(&address, 0, sizeof(QueSelectAddressType));
  address.streetAddress = streetAddress;
  address.city = city;
  address.postalCode = postalCode;
  address.state = state;
  address.country = country;

  err = QueCreatePointFromAddress(&address, &point);
  if (err == queErrNone && point != queInvalidPointHandle) {
    err = QueViewPointOnMap(point);
  }

  QueAPIClose(QueCallback);
  return err;
}

The Garmin Mobile XT SDK works in a straight forward way. Before making any calls to the API, you first need to Open it. Once open, you can start executing a series of methods and then once you're done you must Close the API. The Garmin Mobile XT has to be running before you can execute commands to it, otherwise you will get a communication error.

You might notice in the code above an empty static method called QueCallback(QueNotificationT8 aNotification). This is a callback method that receives the information about the application state. You can use this for making callbacks from native to managed code. You can pass a delegate method from managed code to the native methods that expect QueNotificationCallback as a parameter. We will only use it for debugging purposes in this example. We will not dig more into that in this article.

Normally when reverse geocoding an address to a GPS coordinates using some free service, you will get the coordinates in decimal degrees (WGS84 decimal format). Navigating to a coordinate using the Garmin Mobile XT SDK requires the coordinates to be in semicircles (2^31 semicircles equals 180 degrees).

To convert decimal degrees to semicircles we use the following formula:
semicircles = decimal degrees * (2^31 / 180)


Managed wrapper

In my article Integrating with TomTom Navigator, I created a Generic Navigator wrapper that uses the INavigator interface for defining methods to be used by the managed wrapper. The purpose of the Generic Navigator was to allow the application to integrate with several navigation solutions without changing any of the existing code. As I already discussed this in the past, I will skip this part and only focus on how to integrate with Garmin Mobile XT.

We first need to create an enumeration containing error codes we receive from the native wrapper.

public enum GarminErrorCodes : int
{
  None = 0,
  NotOpen = 1,
  InvalidParameter,
  OutOfMemory,
  NoData,
  AlreadyOpen,
  InvalidVersion,
  CommunicationError,
  CmndUnavailable,
  LibraryStillOpen,
  GeneralFailure,
  Cancelled,
  RelaunchNeeded
}

We of course need to create our P/Invoke declarations. This time let's put them in an internal class called NativeMethods()

internal class NativeMethods
{
  [DllImport("Garmin.Native.dll")]
  internal static extern int CloseAPI();

  [DllImport("Garmin.Native.dll")]
  internal static extern int OpenNavigator();

  [DllImport("Garmin.Native.dll")]
  internal static extern int NavigateToAddress(
    string address,
    string city,
    string postalcode,
    string state,
    string country);

  [DllImport("Garmin.Native.dll")]
  internal static extern int NavigateToCoordinates(
    double latitude,
    double longitude);

  [DllImport("Garmin.Native.dll")]
  internal static extern int ShowAddressOnMap(
    string address,
    string city,
    string postalcode,
    string state,
    string country);
}

Let's create a .NET exception that we can throw which contains native error details when a native method call fails. Let's call it GarminNativeException()

[Serializable]
public class GarminNativeException : Exception
{
  public GarminNativeException() { }

  public GarminNativeException(GarminErrorCodes native_error) { }

  public GarminNativeException(
    string message,
    GarminErrorCodes native_error) : base(message) { }
}

Now we need a class that we can use for calling the wrapped managed methods to the Garmin mobile XT. Let's call it GarminXT()

public class GarminXT : IDisposable
{
  public void Dispose()
  {
    NativeMethods.CloseAPI();
  }

  public void OpenNavigator()
  {
    GarminErrorCodes err = (GarminErrorCodes)NativeMethods.OpenNavigator();

    if (err != GarminErrorCodes.None) {
      ThrowGarminException(err);
    }
  }

  public void NavigateToAddress(
    string address,
    string city,
    string postalcode,
    string state,
    string country)
  {
    GarminErrorCodes err = (GarminErrorCodes)NativeMethods.NavigateToAddress(
      address,
      city,
      postalcode,
      state,
      country);

    if (err != GarminErrorCodes.None) {
      ThrowGarminException(err);
    }
  }

  public void NavigateToCoordinates(double latitude, double longitude)
  {
    GarminErrorCodes err = (GarminErrorCodes)NativeMethods.NavigateToCoordinates(
      latitude,
      longitude);

    if (err != GarminErrorCodes.None) {
      ThrowGarminException(err);
    }
  }

  public void ShowAddressOnMap(
    string address,
    string city,
    string postalcode,
    string state,
    string country)
  {
    GarminErrorCodes err = (GarminErrorCodes)NativeMethods.ShowAddressOnMap(
      address,
      city,
      postalcode,
      state,
      country);

    if (err != GarminErrorCodes.None) {
      ThrowGarminException(err);
    }
  }

  private GarminNativeException ThrowGarminException(GarminErrorCodes err)
  {
    string message = string.Empty;

    switch (err) {
      case GarminErrorCodes.NotOpen:
        message = "Close() called without having Open() first";
        break;
      case GarminErrorCodes.InvalidParameter:
        message = "Invalid parameter was passed to the function";
        break;
      case GarminErrorCodes.OutOfMemory:
        message = "Out of Memory";
        break;
      case GarminErrorCodes.NoData:
        message = "No Data Available";
        break;
      case GarminErrorCodes.AlreadyOpen:
        message = "The API is already open";
        break;
      case GarminErrorCodes.InvalidVersion:
        message = "The API is an incompatible version";
        break;
      case GarminErrorCodes.CommunicationError:
        message = "There was an error communicating with the API";
        break;
      case GarminErrorCodes.CmndUnavailable:
        message = "Command is unavailable";
        break;
      case GarminErrorCodes.LibraryStillOpen:
        message = "API is still open";
        break;
      case GarminErrorCodes.GeneralFailure:
        message = "General Failure";
        break;
      case GarminErrorCodes.Cancelled:
        message = "Action was cancelled by the user";
        break;
      case GarminErrorCodes.RelaunchNeeded:
        message = "Relaunch needed to load the libraries";
        break;
      default:
        break;
    }

    throw new GarminNativeException(message, err);
  }
}

The managed wrapper GarminXT() implements IDisposible for ensuring that the API will be closed when the GarminXT object gets disposed. I check the return code of every method to verify if the native method call succeeded or failed. If the native method call failed then I throw a GarminNativeException containing a text description of the error and the GarminErrorCode returned by the native method call.


Using the Managed Wrapper

Now that we have a managed wrapper for the Garmin Mobile XT SDK we can start testing it with a simple smart device application. Let's say that we created a simple application that accepts street address, city, postal code, country, latitude, longitude. We also have some buttons or menu items for: Navigating to an address, Navigating to coordinates, Showing an address on the map, and for launching Garmin Mobile XT.

Since the managed wrapper implements IDisposable, we surround our calls to it with the using statement:

using (GarminXT xt = new GarminXT()) {
  xt.OpenNavigator();
}

As I mentioned before, it is important that Garmin Mobile XT is running in the background for executing certain commands. Otherwise the managed Garmin XT wrapper will throw a GarminNativeException saying that there was an error communicating with the API. I would suggest handling the GarminNativeException everytime calls to the managed wrapper are made.

For launching Garmin Mobile XT:

try {
  using (GarminXT xt = new GarminXT()) {
    xt.OpenNavigator();
  }
}
catch (GarminNativeException ex) {
  Debug.Assert(false, ex.Message, ex.StackTrace);
}

For navigating to an address:

try {
  using (GarminXT xt = new GarminXT()) {
    xt.NavigateToAddress(
      "Hørkær 24",
      "Herlev",
      "2730",
      null,
      "Denmark");
  }
}
catch (GarminNativeException ex) {
  Debug.Assert(false, ex.Message, ex.StackTrace);
}

For navigating to coordinates:

try {
  using (GarminXT xt = new GarminXT()) {
    xt.NavigateToCoordinates(
      55.43019,
      12.26075);
  }
}
catch (GarminNativeException ex) {
  Debug.Assert(false, ex.Message, ex.StackTrace);
}

For showing an address on the map:

try {
  using (GarminXT xt = new GarminXT()) {
    xt.ShowAddressOnMap(
      "Hørkær 24",
      "Herlev",
      "2730",
      null,
      "Denmark");
  }
}
catch (GarminNativeException ex) {
  Debug.Assert(false, ex.Message, ex.StackTrace);
}


That wasn't that hard was it?

But there is one thing that I don't quite understand. Why do we have to wrap SDK's like this ourselves? Why don't they just provide managed SDK's? Hopefully this will change in the near future. Until then, I guess I can just write a few more articles about it.

36 comments:

  1. Hi Christian, great stuff!

    Can you make the dll available for download?

    ReplyDelete
  2. Sure. Give me a few days, I don't have any place to host them yet.

    I can email them to you if you need them immediately.

    ReplyDelete
  3. Please email to nospam at thebyteworks dot com.
    Thanks!

    ReplyDelete
  4. Hi Chistian, Do you have the projects available? I haven't been able to get this to work. If I create my own functions they work but when I add the QueApi functions nothing works (PInvoke can't find DLL). If available please email to jlb521@gmail.com

    Much appreciated. JB

    ReplyDelete
  5. Ehi great stuff!

    Can you tell where i can download your dll?

    thank you very much in advance!

    ReplyDelete
  6. You can download the solution from this link:
    http://cid-ca531e7fb4762c70.skydrive.live.com/self.aspx/Public/GarminXT.zip

    ReplyDelete
  7. Hi Christian,

    Very interesting work you did here. Maybe you have a suggestion for the following issue.

    I want to be able to mute and unmute the Garmin voice to be able to, for instance, play an mp3.

    From what I understand the Garmin voice is linked directly to system volume and there is no API for controlling the volume.

    Does anyone perhaps have any suggestions on this?

    Thanks, keep up the good work.

    Wouter

    ReplyDelete
  8. Hi Christian,

    You did really a great job....

    but I am in need to include somthing in it.

    I also want to return Longitude and Latitude to my program.

    Can you please include class for it.
    I tried it with lots of idea but did not got success...

    Thanks

    ReplyDelete
  9. Hi,

    This code is great - I'm using it from my own app and it works brilliantly.

    Only problem is that occasionally, on exiting Garmin, my app has been hidden by the WM OS.

    I've tried setting topmost on my app to true before opening Garmin but this then makes my form over the top of Garmin permamently.

    Is there a way of keeping my app running just behind the Garmin window so when a user quits or hides the Garmin app then they are returned to my app?

    Cheers,
    Adam

    ReplyDelete
  10. HI Adam Kimber,

    Are you able to get GPS data (lat /long) from Garmin device...if yes ..please help me...

    my email id is bhaskarbhardwaj82@gmail.com

    please send me the code or just tell me the way to get data from device

    ReplyDelete
  11. Hi Chistian i have the same problem as Joshua but cant find any response i am very interrested in seeing your product work, could you please point myself in the right direction, my email address is adlaws@gmail.com

    Many regards

    Andrew

    ReplyDelete
  12. Hi Chistian i have the same problem as Joshua could you please point me in the right direction

    ReplyDelete
  13. Hi Everybody,

    Finally i am able to handle all garmin functions through codes.

    I am using Garmin XT with Windows mobile 5.0 and 6.0

    Thanks alot..to Christian...

    if any body have issues ....they can write it to me directly...i hope i will be able to solve it...

    bhaskar_bhardwaj@rediffmail.com

    ReplyDelete
  14. This comment has been removed by the author.

    ReplyDelete
  15. Hi Christian,

    Thank you for your work on dll. It really helped us get started with Garmin SDK. We are able to execute the 'OpenNavigator' call but are stuck at other calls like NavigateToCoordinate, NavigateToAddress as the system throws the communication error "There was an error communicating with the API". We tried your instruction related to the exception handling and 'using' call. But it does not seem to work. Can you please help us out with this?

    Regards
    Mihir

    ReplyDelete
  16. Hi Christian

    Thanks for the code download
    I have one issue.
    I am using the downloaded dll's and testApp on a windows mobile 6.0 device. when I try to run any of the testApp functions such as Launch
    Garmin I get the following error

    Can't find Pinvoke.Dll
    Garmin.native.dll

    any suggestions would be great

    thanks
    Colm

    ReplyDelete
  17. You will have to deploy the output of C++ project included in my sample.

    -Christian

    ReplyDelete
  18. Nice article, Christian!
    Thank you.

    ReplyDelete
  19. Hi Christian

    great article, thank you.
    I am working with the Pc version (Garmin Mobile PC), and I have come across some strange things. Using the queapi.dll, it fails on navigating to Hørkær 24, 2730 Herlev. It seems that the address translation is very weak (you must specify city name, and in Copenhagen, it must be "Ydre Østerbro" instead of just "København")

    Since you obviously have worked with the Garmin integration, have you come across any weird behaviours such as this?

    Thanks!

    ReplyDelete
  20. Hi Robert,

    Funny, I was working at Hørkær 24, 2730 Herlev when I wrote this :)

    I actually didn't get to test much of the navigation features of Garmin at that point. From what I remember I made the wrapper and tested basic features of it, mostly wrapper specific.

    But isn't using a postal code good enough? I remember in TomTom, I had trouble navigating to a place called Søborg hovedgade, Søborg. Coz apparently the town is called Gladsaxe. There is a Søborg hovedgade xx both in Gentofte and Gladsaxe

    ReplyDelete
  21. Hi Christian

    Wow - fast response :-)

    In the Garmin Mobile PC GUI, it works just fine (you can even search on street name only, and it will list the cities with matching street names - pretty good, actually).

    BUT! When I try to do the same, the doc for the API says that you can leave "certain" fields as NULL values, and it should then ignore them. However, it has turned out to be rather picky with the city names (in plain English: No city name, no hit!). Also, when you do search through the GUI, it actually says "Ydre Østerbro" and it doesn't even mention København.

    I have contacted GARMIN support, and will let you know if they come up with a solution (and if so, I will let you know also).

    Well, at least I am not the only one in the world to use the API (although it is very hard to find people who are using it for the PC version).

    Thanks again
    Best regards
    Robert

    ReplyDelete
  22. Thanks Robert! That would be highly appreciated!

    ReplyDelete
  23. Hi Christian

    So, Garmin came back to me with a not too surprising response:

    Thank you for contacting Garmin Europe.

    With regards to your email below, to the mobile PC software we don?t
    have SDK package we only have SDK for the mobile XT page which is
    different to the mobile PC software I have attached a link below for a
    SDK

    http://developer.garmin.com/

    As we don?t have SDK page for the mobile PC then we will not be able to
    help you on this. We can only support the mobile PC as the normal
    package.

    If there is anything else I can help you with then please let me know.
    Alternatively you can search for a solution here: http://www.garmin.com/support


    In the meantime, I have made a work-around, and I will simply navigate to coordinates instead (moving the problem of resolving addresses to another system).

    So the good news: the API works on GMPC, the bad: navigating to addresses works ONLY if you have the GARMIN representation of the address :-(

    Thank you for your interest in this, and have a great week-end.

    Best regards
    Robert

    ReplyDelete
  24. Christian, This is awesome. Thank you. It looks like just what I need, but I am having 1 problem: When I run the software, OpenNavigator connects without an error, but all navigate functions return the invalid version error. Has anyone else run into this? I migrated the code to VS 9 and compiled the Garmin.Native project to WM5 so it runs on device.

    Thanks for any help.

    Dave

    ReplyDelete
  25. Hi Dave, I don't think I've ever experienced getting a version error. I remember that the garmin mobile xt application should be running before you can execute navigational commands.

    Have you tried contacting the Garmin support on this?

    ReplyDelete
  26. I HAVE SOLVED SOME OF THE PROBLEM.

    Problem #1 : Cannot Find P/Invoke dll Garmin.Native.dll.

    Solved: Install Garminxt software in your application, without a software Sdk will not be able to Communicate Garmin.
    Installation Steps.
    http://dextrouzst.blogspot.com/2009/09/how-to-install-garmin-mobile-xt-on.html

    After Installing this, now GarminSdk will works fine.



    Problem #2: Some People click functions like NavigateToCoordinate before click Garmin Launch.
    Solved: You have to Launch Garmin First.

    Problem #3: Cannot find Garmin.Native.dll

    Solved : Put Garmin.Native.dll in your Cab Setup.
    In ApplicationFolder Add File then Add Garmin.Nativ.dll in your Cab.

    ReplyDelete
  27. hi Christian,
    Is there any function available in Garmin Sdk, where we pass Pickup and destination point and garmin will create routes from it.
    Currently there is only NavigateToCoordinate Function available in it.

    I want to Pass Mulitple Destination in it.

    ReplyDelete
  28. Hi Christian,

    i'm trying Run/Compile Garmin.Native.dll on Win32 (with GarminMobilePC) but after four days of hard work i don't have good results.

    Could you help me with any tip, with the compiled dll or Project on c++ ready to compile on Win32?

    Sorry for my limited english,

    Thanks in advance

    Ed.


    ReplyDelete

  29. Hi Cristian!
    Thanks for you response and help,

    When i tried compile the project on Visual Studio 2005 c++, before the compilation i changed target destination of project to Win32, after compilation, the error is on "#include " (line 14 of stdafx.h), when i tried delete this line, i had an error storm.

    C++ is not my expertise, i'm trying compile this lib for using it from c# because i'm used Pocket PC version some years before and it works very well.

    Now i'm wanting to use Garmin Mobile PC into C# application for an Ham Radio Experiment.

    Thanks again for you help,

    best regards

    Ed.

    ReplyDelete
  30. Hello,

    The company I currently work for uses Garmin Mobile XT 4.0 but is looking to upgrade to 5.0 but because both of these are no longer supported by Garmin we are unable to find any API documentation for 5.0. Just wondering if anyone here has this and would be willing to send it to me.

    Please and thank you.

    Eric

    ReplyDelete
  31. Hi Eric,

    Unfortunately I can't seem to find the SDK for Garmin Mobile XT. It's been around 5 years since I last played with it.

    Sorry...

    ReplyDelete
  32. Hi Christian this is a very great tutorial. I have downloaded your solution and run it on Windows Mobile 6.5 however when i call QueAPIOpen it always return queErrInvalidVersion. Does it mean windows mobile 6.5 is not supported by the SDK? I know it sounds like a silly questions, appreciate your help.

    ReplyDelete