{"id":296,"date":"2008-08-25T11:26:35","date_gmt":"2008-08-25T11:26:35","guid":{"rendered":"http:\/\/dalelane.co.uk\/blog\/?p=296"},"modified":"2008-08-25T13:59:11","modified_gmt":"2008-08-25T13:59:11","slug":"gps-intermediate-driver-for-windows-mobile-and-getting-it-to-work","status":"publish","type":"post","link":"https:\/\/dalelane.co.uk\/blog\/?p=296","title":{"rendered":"GPS Intermediate Driver for Windows Mobile (and getting it to work!)"},"content":{"rendered":"<p>My last two Windows Mobile phones have both had GPS, so I&#8217;ve played with code to get my location from the GPS a few times. These have generally been quick hacked-together bits and pieces. In all of them, I wrote my own GPS code. I knew that the GPS device would be accessible through a serial port, so I just connected to the relevant COM port and started reading. It&#8217;s easy to parse &#8211; NMEA sentences are written with an update on each line, in comma-separated strings. <\/p>\n<p>After sharing <a href=\"http:\/\/dalelane.co.uk\/blog\/?p=294\" target=\"_blank\">my OpenCellId client<\/a> last week, I was encouraged to try rewriting the GPS code for it using the Windows Mobile GPS Intermediate Driver. So I had a quick try. I was vaguely aware of it before, but hadn&#8217;t taken a proper look. In this post, I&#8217;ll describe briefly what it is, it&#8217;s benefits over home-grown hacks such as my own, and share a couple of things that I had to do to get it to work.<\/p>\n<p><!--more--><strong>What is the GPS Intermediate Driver?<\/strong><br \/>\nThe <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms894898.aspx\" target=\"_blank\">GPS Intermediate Driver<\/a> is a Windows Mobile DLL providing a GPS API. Instead of accessing the serial port yourself, you call GPS functions to get the data via the OS. You can even get access to the data <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms889503.aspx\" target=\"_blank\">already parsed<\/a> into <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms893621.aspx\" target=\"_blank\">location information<\/a>.<\/p>\n<p><strong>Why use it?<\/strong><br \/>\nMSDN documents <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms889962.aspx\" target=\"_blank\">several benefits<\/a>, but I think that the main ones are:<\/p>\n<ul type=\"square\">\n<li>By not accessing the COM port yourself, you don&#8217;t need to know which COM port to use. I have already had a few emails from people asking how to work out what value to put into the COM port box on the OpenCellId client. Using the GPS Intermediate driver means that I&#8217;ve been able to remove this setting &#8211; I don&#8217;t need to know how a user&#8217;s GPS device is configured.\n<\/li>\n<li>By not keeping the COM port to yourself, you allow the possibility for multiple applications to access the GPS device at the same time. Using the GPS Intermediate driver means that a user could potentially run the OpenCellId client at the same time as a sat nav program.\n<\/li>\n<\/ul>\n<p>These were enough of a benefit to justify throwing out my serial port and string parsing code.<\/p>\n<p><strong>How do I use it?<\/strong><br \/>\nIf you want to access it from unmanaged, C++ code, the <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms850332.aspx\" target=\"_blank\">API is well-documented<\/a> in MSDN. <\/p>\n<p>If you want to access it from managed, C# code, the Windows Mobile 5 and 6 SDKs each ship with a <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/bb158708.aspx\" target=\"_blank\">sample project which uses the GPS Intermediate driver<\/a>.<\/p>\n<p><strong>How did I get it to work?<\/strong><\/p>\n<ol>\n<li>Avoid the WM5 sample &#8211; use the WM6 one<br \/>\nWindows Mobile 6 is still not completely widespread, so I try to target WM5 to make my code useful to as many devices as possible. So I started with the GPS sample from the WM5 SDK.<\/p>\n<p>It is a little buggy. The code threw a few DivideByZero exceptions, but even after fixing those I couldn&#8217;t get any useful values from it. <\/p>\n<p>I had a look at the WM6 sample &#8211; doing a diff between the two projects shows that these bugs were found and corrected in time for the WM6 SDK. It looks like there is a bug in the WM5 sample in the expectation of the format of the latitude and longitude values &#8211; with some parsing methods in the WM6 sample being rewritten.<\/p>\n<p>I couldn&#8217;t find anything in the WM6 sample that is unsupported in WM5, so (rather than keep trying to fix the WM5 sample!) I took a copy of the WM6 sample and used &#8220;<a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms180809.aspx\" target=\"_blank\">Change target platform<\/a>&#8221; to repurpose it for WM5. It compiles fine without needing any changes. <\/p>\n<\/li>\n<li>Handling newer WM6 devices<br \/>\nEven then, I couldn&#8217;t get anything out of it &#8211; each attempt to get a location returned latitudes and longitudes of zero. <\/p>\n<p>Stepping through the code with the Visual Studio debugger tracked down the problem to the call to <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/bb202050.aspx\" target=\"_blank\">GPSGetPosition<\/a>. It was returning an error code of 87 : ERROR_INVALID_PARAMETER. This was a surprise &#8211; why would the Microsoft sample code be using invalid parameters for the main GPS API call?<\/p>\n<p>A bit of Googling uncovered <a href=\"http:\/\/blogs.msdn.com\/cenet\/archive\/2007\/12\/06\/gpsid-problem-workaround-on-recent-wm6-release.aspx\" target=\"_blank\">an MSDN blog post with the answer<\/a>. The blog post is worth a read if you&#8217;re interested in the details, but in short, one of the structures used in the GPS code has changed in newer WM6 builds, and when allocating space for the structure the sample was using a hard-coded value for the old size of the structure. <\/p>\n<p>The new structure size is 376 bytes. I tested this with a quick hack to use a structure size of 376 if a first attempt fails with a return code of ERROR_INVALID_PARAMETER, which lets it get location updates okay. <\/p>\n<p>Stepping through with the debugger shows that we do go through this hack on my WM6 &#8216;Advantage&#8217;, but go through the original code on an old WM5 device of mine. <\/p>\n<pre style=\"font-size: 0.7em; border: thin solid silver; background-color: #eeeeee; padding: 0.7em; overflow: scroll;\"><code>public GpsPosition GetPosition(TimeSpan maxAge)\r\n{\r\n    GpsPosition gpsPosition = null;\r\n    if (Opened)\r\n    {\r\n        \/\/ allocate the necessary memory on the native side.  We have a class (GpsPosition) that \r\n        \/\/ has the same memory layout as its native counterpart\r\n        IntPtr ptr = Utils.LocalAlloc(Marshal.SizeOf(typeof(GpsPosition)));\r\n\r\n        \/\/ fill in the required fields \r\n        gpsPosition = new GpsPosition();\r\n        gpsPosition.dwVersion = 1;\r\n        gpsPosition.dwSize = Marshal.SizeOf(typeof(GpsPosition));\r\n\r\n        \/\/ Marshal our data to the native pointer we allocated.\r\n        Marshal.StructureToPtr(gpsPosition, ptr, false);\r\n\r\n        \/\/ call native method passing in our native buffer\r\n        int result = GPSGetPosition(gpsHandle, ptr, 500000, 0);\r\n        if (result == 0)\r\n        {\r\n            \/\/ native call succeeded, marshal native data to our managed data\r\n            gpsPosition = (GpsPosition)Marshal.PtrToStructure(ptr, typeof(GpsPosition));\r\n\r\n            if (maxAge != TimeSpan.Zero)\r\n            {\r\n                \/\/ check to see if the data is recent enough.\r\n                if (!gpsPosition.TimeValid || DateTime.Now - maxAge > gpsPosition.Time)\r\n                {\r\n                    gpsPosition = null;\r\n                }\r\n            }\r\n        }\r\n        else if (result == 87) \/\/ ERROR_INVALID_PARAMETER)\r\n        {\r\n            \/\/ \r\n            \/\/ TEMPORARY HACK\r\n            \/\/ \r\n            \/\/  http:\/\/blogs.msdn.com\/cenet\/archive\/2007\/12\/06\/gpsid-problem-workaround-on-recent-wm6-release.aspx\r\n            \/\/ \r\n\r\n            Utils.LocalFree(ptr);\r\n\r\n            \/\/ allocate the necessary memory on the native side.  We have a class (GpsPosition) that \r\n            \/\/ has the same memory layout as its native counterpart\r\n            ptr = Utils.LocalAlloc(376);\r\n\r\n            \/\/ fill in the required fields \r\n            gpsPosition = new GpsPosition();\r\n            gpsPosition.dwVersion = 1;\r\n            gpsPosition.dwSize = 376;\r\n\r\n            \/\/ Marshal our data to the native pointer we allocated.\r\n            Marshal.StructureToPtr(gpsPosition, ptr, false);\r\n\r\n            \/\/ call native method passing in our native buffer\r\n            result = GPSGetPosition(gpsHandle, ptr, 500000, 0);\r\n\r\n            if (result == 0)\r\n            {\r\n                \/\/ native call succeeded, marshal native data to our managed data\r\n                gpsPosition = (GpsPosition)Marshal.PtrToStructure(ptr, typeof(GpsPosition));\r\n\r\n                if (maxAge != TimeSpan.Zero)\r\n                {\r\n                    \/\/ check to see if the data is recent enough.\r\n                    if (!gpsPosition.TimeValid || DateTime.Now - maxAge > gpsPosition.Time)\r\n                    {\r\n                        gpsPosition = null;\r\n                    }\r\n                }\r\n            }\r\n        }\r\n\r\n        \/\/ free our native memory\r\n        Utils.LocalFree(ptr);\r\n    }\r\n\r\n    return gpsPosition;    \r\n}<\/code><\/pre>\n<p>The <a href=\"http:\/\/blogs.msdn.com\/cenet\/archive\/2007\/12\/06\/gpsid-problem-workaround-on-recent-wm6-release.aspx\" target=\"_blank\">MSDN blog post<\/a> suggests that <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/bb202076.aspx\" target=\"_blank\">GPSGetDeviceState<\/a> has a similar problem, but I&#8217;ve not tried it for myself.\n<\/li>\n<\/ol>\n","protected":false},"excerpt":{"rendered":"<p>My last two Windows Mobile phones have both had GPS, so I&#8217;ve played with code to get my location from the GPS a few times. These have generally been quick hacked-together bits and pieces. In all of them, I wrote my own GPS code. I knew that the GPS device would be accessible through a [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[266,263,138,264,262,46,265,257,19,43],"class_list":["post-296","post","type-post","status-publish","format-standard","hentry","category-code","tag-error_invalid_parameter","tag-gid","tag-gps","tag-gps-intermediate-driver","tag-gpsgetposition","tag-mobile","tag-msdn","tag-opencellid","tag-windows-mobile","tag-windowsmobile"],"_links":{"self":[{"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts\/296","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=296"}],"version-history":[{"count":0,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts\/296\/revisions"}],"wp:attachment":[{"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=296"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=296"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=296"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}