Welcome back! I'm going to continue with my overview of Navigation in MOSS 2007. If you missed part one, I'd definitely suggest reading that first. We're going to start off today by looking at the configuration options available on the Navigation Settings page which is accessible through the Site Actions menu or off of the Site Settings page.

Navigation Settings

PortalSiteMapProviders examine inheritance settings and various other navigation related properties defined on Web sites in order to provide data. Navigation is very site-centric, and each Web site in a site collection can determine the way the navigation items beneath it are displayed, all the way down to the next site(s). As such, each site has a variety of navigation options that can be manipulated at the Navigation Settings page, accessible through the Site Actions menu. Below is a screenshot of this page for Web Site - 2.

The first two options, "Show subsites" and "Show pages", control whether or not these two types of items are automatically included in navigation. Note that disabling both of these options won't result in each and every Web site and Web page disappearing from navigation. Rather, beneath the particular Web site we're dealing with (Web Site - 2), items of these types (Web Site - 2.1, Web Site - 2.2, Web Page - 2.P1) won't show up.

The Sorting options are fairly self-explanatory. Selecting manual sorting allows you to rearrange the items manually using the sorting pane in a different section we'll look at shortly. Selecting automatic sorting will result in these nodes being arranged for you using the specified sorting criteria although you can still use the sorting pane to move items from one heading to another.

The next two sections of options have more interesting consequences and will allow us to touch back to the discussion about PortalSiteMapDataSource and the StartFromCurrentNode property from earlier. Note that these sections are not shown when visiting the root Web site's Navigation Settings page so check out this page for a subsite of the root. Two options are available pertaining to global navigation. The first, "Display the same navigation items as the parent site", is the default for non-root sites and indicates that the Web site should use whichever global navigation items its parent site does. This is also referred to as "inheriting" navigation items. The second, "Display the navigation items below the current site", indicates that this site's global navigation items should be populated from the items below the site. There are similar options for current navigation although the default value is notably different. As I mentioned, these sections are not shown for the root site and this is because the root site has no parent to inherit navigation items from and therefore must display its own navigation items.

These settings affect where in the hierarchy the data source starts from when visiting the site in question. As noted before, even though the data source declaration includes StartFromCurrentNode="true", the MOSS data source walks up hierarchy from the current node until it finds a Web site that does not inherit and starts from there instead. If the data source is attached to a Global provider, it looks to the global inheritance settings whereas a Current type provider looks at the current inheritance settings.

The default settings cause global navigation to inherit in most cases, eventually starting from the root site (which obviously cannot inherit). Changing this setting for a specific site somewhere down the hierarchy causes global navigation to be "re-anchored" for that site and any beneath it. For example, take a look at the screenshots below, taken while visiting Web Site - 2 and Web Site - 2.1 after switching Web Site - 2's global navigation to "Display the navigation items below the current site".

In both cases, the global navigation now starts from Web Site - 2 and display the items beneath it.

Current navigation, on the other hand, is not set to inherit by default and so usually does start at the current node. This also means that setting current navigation to "Display the same navigation items as the parent site" for a particular site will usually cause the data source to start just one site up, rather than follow an inheritance chain all the way to the root site. The middle option for current navigation, "Display the current site, the navigation items below the current site, and the current site's siblings" is a slight twist on things. Rather than starting from the current site, the data source's starting node is moved up exactly one level, so as to include the current site and the site's siblings, and any children of the sibling nodes are automatically trimmed out. This effect can be seen in the above screenshots on the vertical navigation menus (which is hooked up to a Current provider). Looking at the image on the left, even though Web Site - 1 and Web Site - 3 have child nodes beneath them we are currently visiting Web Site - 2 and therefore the mentioned sites are siblings of the current site and have their children trimmed.

The next section in the settings page includes the navigation editing and sorting pane. With this you can rearrange any subsites and pages of this particular Web site, as well as manually add new links and headings. Headings may link to somewhere or may be unlinked and they aren't just for grouping links; you can group sub-sites and pages under headings as well. The control also has some advanced accessibility features. You can change your selection using the [Up-Arrow] and [Down-Arrow] keys, move the selected item up or down using [Ctrl]+[UpArrow] or [Ctrl]+[DownArrow], and you can also use [Tab] and [Shift]+[Tab] to move between the selected item and the toolbar. Users with screen readers can activate WSS's "More Accessible Mode" to enable a rendering that is more conducive to this method of interaction.

Above, I added a "Pages" heading using the "Add Heading..." and moved the only page in this site under it by selecting the Page and clicking "Move Up" and "Move Down". After clicking "OK", the navigation menus on the site look like this:

So our heading shows up, but what about the page beneath it? Further, why does it show up only in the vertical menu and not the horizontal menu? First, the page isn't showing up because this would require 3 levels of hierarchy to be shown by the menu: Web Site - 2, Pages, Web Page - 2.P1. We could simply add one more static or dynamic level to the menu declaration but I want to keep my menu showing only 2 levels. Instead I'll go back to the Navigation Settings page and, in the Current Navigation section, choose to "Display only the navigation items below the current site", resulting in the following:

I no longer see Web Site - 2 and its siblings, but I can see more of the items beneath the current site now. As for the issue of this heading not showing up in the top navigation menu, we'll need to return to the data source declaration we looked at earlier, particularly the last two properties:


...
TrimNonCurrentTypes="Heading"
TreatStartingNodeAsCurrent="true" />

The first of these properties specifies that any items of type Heading not directly beneath the current node should be trimmed. Now at first it seems like this criterion is met: we are visiting Web Site - 2 and the Pages heading is directly beneath it. However, the second property specifies that the current node for trimming purposes should not be the currently visited item, but should be whichever node the data source starts from. In this case, the data source is starting from the root site and so only Headings directly beneath the root site will be shown.

There are number of available trimming options that will include a larger subset of the headings, and you can turn off trimming completely by removing reference to any of these trimming properties. See the previous post for information about these trimming options.

Lastly, I wanted to point out a somewhat confusing detail. If we return to the Navigation Settings page for just a moment, you'll notice that toggling between the two Global Navigation options results in a change in the way that the editing and sorting pane is displayed. When the option is set to "Display the navigation items below the current site" the pane is split into a Global Navigation and Current Navigation sections allowing separate configuration of global navigation items and current navigation items. However, with "Display the same navigation items as the parent site" there is no such separation:

"Display the same navigation items as the parent site"

"Display the navigation items below the current site"

This behavior is linked to the Combined provider which is usually used to drive the top navigation menus. You may recall that the Combined provider uses items and settings from global navigation when the site does not inherit its global navigation items, and uses the items and settings from current navigation when the site does inherit global navigation. This means that it is useless to try and arrange items in Global navigation if the site inherits global navigation because the current navigation items will be used instead.

You may then wonder about the Global type provider that I earlier recommended you not use. Won't this provider always use the items and settings from global navigation? Shouldn't I have a choice about whether or not I want to configure global and current navigation differently? This was the original intent but as it turns out there are restrictions on WSS's SPNavigation store which do not allow the storage of items in the TopNavigationBar while the site is set to inherit global navigation (note: for those familiar with WSS navigation, this is equivalent to having SPNavigation.UseShared set to true for a particular SPWeb). As a result, it is not possible to configure global navigation separately from current navigation unless the site displays its own global navigation items, rendering the Global provider useless (although it is used internally, so don't remove the Global provider definition from the web.config).

At this point we've completed a fairly thorough trip through the navigation features in MOSS 2007. I hope you'll find this information useful in getting navigation on your MOSS site to behave just how you want it to. Please feel free to leave questions and comments and I'll do my best to respond wherever possible.

Hi everyone. Today I want to take some time to walk you through some features of navigation in MOSS 2007 from an implementation perspective, and show you some things you can do to customize your Web site's navigation.

First, let's explore some of the navigation-related features in MOSS 2007. I'll start off by provisioning a new site collection based on the Publishing Portal template. I've created a few sub-sites and pages in my site collection, with a hierarchy like so:

Root Site (NavigationExample)
Web Site - 1
Web Page - 1.P1
Web Site - 2
Web Site - 2.1
Web Site - 2.1.1
Web Page - 2.1.P1
Web Page - 2.1.P2
Web Site - 2.2
Web Page - 2.P1
Web Site - 3
Web Site - 3.1

Let's take a look at what gets setup automatically for you in terms of navigation. When I visit the Web Site- 2 subsite I see the following:

Menu Control

Let's first talk about the horizontal or top navigation menu. This menu is declared on the master page that this site is using (I've pulled out the markup below):


DataSourceID="GlobalNavDataSource"
Orientation="Horizontal"
StaticDisplayLevels="1"
MaximumDynamicDisplayLevels="1" />

The vertical or left navigation menu is declared in a similar way, but with slightly different properties. Note that I removed some of the less interesting properties (at least for this discussion) like styling so as to focus on the more interesting ones. Also, these properties are identical to those available on the ASP.NET 2.0 Menu control (http://msdn2.microsoft.com/en-us/library/system.web.ui.webcontrols.menu.aspx).

  • DataSourceID is probably the most important property here because it specifies the control which will actually provide the hierarchical data for this menu. In this case it points to a control with ID "GlobalNavDataSource"; we'll take a look at this control shortly.
  • Orientation here is "Horizontal" and I think this is fairly self-explanatory. The other option being "Vertical" which the left menu is set to.
  • StaticDisplayLevels refers to the number of levels of the hierarchy to show in the menu at once. As "1" is specified here, only one level of the hierarchy - the level directly beneath the root - is shown.
  • MaximumDynamicDisplayLevels is similar to StaticDisplayLevels but determines the number of levels to show in dynamic fly-outs. "1" is specified here as well which is why the items beneath Web Site 2 are displayed in a fly-out when the mouse moves over the static item representing Web Site 2.

PortalSiteMapDataSource

Now on to the data source which I found by looking for a declaration with ID="GlobalNavDataSource":


Runat="server"
SiteMapProvider="CombinedNavSiteMapProvider"
ShowStartingNode="false"
StartFromCurrentNode="true"
StartingNodeOffset="0"
TrimNonCurrentTypes="Heading"
TreatStartingNodeAsCurrent="true" />

The PortalSiteMapDataSource is a MOSS-specific data source which knows how to retrieve data from a PortalSiteMapProvider object and expose this data according to the ASP.NET hierarchical data source interface. As such, it specifies the name of the provider it wishes to use to retrieve data via the SiteMapProvider property. We'll look more closely at these providers shortly.

  • ShowStartingNode affects whether or not the starting node is actually returned by the data source. By specifying "false" here, the menu only receives the items beneath the starting node and does not include this node (in this case the root node). Compare the above screenshot to the one below which was captured after switching ShowStartingNode to "true":

As can be seen, the root node, "NavigationExample" is now included in the menu. Also, since I didn't adjust the StaticDisplayLevels, none of the items beneath NavigationExample are included (to see them, simply change this property to "2" to see both NavigationExample and the Web Site - 1, Web Site - 2, etc. items). Whether or not you want to include the starting node is completely a matter of preference. On this site the root Web site is always shown above the horizontal navigation menu, next to the logo (see above), so it doesn't make much sense to include another link in the menu.

  • StartFromCurrentNode affects where the data source starts but things get a little bit interesting here. This property is set to "true" and the current node (i.e. the node representing the item currently being visited) is Web Site - 2. It seems to follow that the data source should start with Web Site - 2 and display the items beneath. However, we've noticed that the menu starts at the root Web site, NavigationExample. So what gives? This is where MOSS navigation starts handling things a little bit differently. By specifying StartFromCurrentNode="true", this is essentially a flag that tells the PortalSiteMapDataSource to apply its own concepts to determine where it should be starting. I'm going to wait until I get to the PortalSiteMapProvider before explaining how this process works, but for now suffice it to say that you should always specify StartFromCurrentNode="true" when using the PortalSiteMapDataSource.
  • TrimNonCurrentTypes (and other similar properties below) allows context- and type-based trimming of nodes. By specifying "Heading" here, the data source is being told that it should remove any nodes of type Heading that are not directly beneath the current node. Multiple types may be specified as a comma-delimited list ("Heading, Page, ..."). Allowable types are Area (this indicates Web or Web site), Page, Heading and AuthoredLink.
  • TrimNonAncestorTypes (not used in above declaration) will trim out any types specified that are not directly beneath the current site or one of its ancestors.
  • TrimNonAncestorDescendantTypes (not used in above declaration) will trim out any nodes of the specified types that are not beneath the current site or one of its ancestor or descendant sites.
  • Finally, TreatStartingNodeAsCurrent is directly related to the previous properties, in that it affects which node is treated as the current node for trimming purposes. As noted before, current node by default refers to the node representing the item currently being visited (Web Site - 2 in our continuing example). By setting this property to "true" the data source's starting node is treated as the context or trimming node (NavigationExample in this case). We'll revisit these trimming-related properties later once we've made it a bit further.

PortalSiteMapProvider

The PortalSiteMapProvider object mentioned above is the true source of the hierarchical data and it provides this to the data source. The PortalSiteMapProvider retrieves nodes from WSS's SPNavigation store which provides the ability to create static links and groupings. These items are then merged with the site collection structure (i.e. dynamic items representing Web sites and Web pages) and then security trimming is applied so that users only see items for which they have permission to navigate.

Named providers are declared in the application's web.config file in order to make them widely accessible. The declarations of the two most important PortalSiteMapProviders are shown below (slightly modified for clarity):


type="Microsoft.SharePoint.Publishing.Navigation.PortalSiteMapProvider"
NavigationType="Combined" EncodeOutput="true" />
type="Microsoft.SharePoint.Publishing.Navigation.PortalSiteMapProvider"
NavigationType="Current" EncodeOutput="true" />

Note that the name of the first provider, "CombinedNavSiteMapProvider", matches the value specified for the SiteMapProvider property of the data source we examined earlier. From this it becomes clear that the horizontal menu is ultimately supplied by this provider object.

Let's talk about these provider properties in more detail:

  • Instances of PortalSiteMapProvider are separated into three types, based on their NavigationType property:
    • Global providers retrieve SPNavigationNode links from the TopNavigationBar SPNavigation collection and respect the inheritance settings specific to global navigation.
    • Current providers retrieve links from the QuickLaunch SPNavigation collection and respect the inheritance settings specific to current navigation.
    • Combined providers act exactly like Global ones when the Web site in question is set not to inherit its global navigation and exactly like Current ones when the site is set to inherit.
    • By default, a Combined provider is hooked up to the horizontal or top menu we've been looking at whereas a Current provider supplies the vertical or left navigation menu. Use of Global providers is not recommended and I'll try to touch on the reasons for this at the end of the post.
  • The EncodeOutput property would probably be more aptly named EncodeTitle, as the only effect of enabling this option is the automatic HTML encoding of the Title property of any nodes returned by the provider. This is useful as ASP.NET's menu control does not automatically HTML encode this property when rendering. ASP.NET's SiteMapPath control, however, does encode this property which is why it's hooked up to the "CurrentNavSiteMapProviderNoEncode", which is declared identically to the "CurrentNavSiteMapProvider", except without the EncodeOutput="true" specification.

Other properties available on the provider but not shown in the above declarations include:

  • DynamicChildLimit, which is an integer property that specifies the maximum "dynamic" children for each Web site (dynamic children include subsites and pages). This value defaults to 50 so if the number of subsites and pages for a particular Web site is greater than 50 objects will be left out unless you adjust this setting. This limit can be increased but keep in mind the usability of a navigation hierarchy that has so many children at each pivot.
  • RequireUniqueKeysForNodes, which is a Boolean property that controls whether or not the nodes returned from the provider should all have unique values for their Key properties. Yes, I know the term "Key" itself implies uniqueness, but in order to get menu highlighting on authored links and headings working just right we needed to make this property default to false. This won't cause any problems when attaching (through a data source) to an ASP.NET menu control, but for most other display controls make sure the provider declaration includes RequireUniqueKeysForNodes="true".

There are also a group of properties that control the provider's inclusion of various node types:

  • IncludeSubSites and IncludePages, properties which may be set to Always (ignore the per-Web site setting, always include this type), PerWeb (respect the per-Web site setting, this is the default and we'll see how to change the per-Web setting later), and Never (ignore the per-Web site setting, never include this type).
  • IncludeHeadings and IncludeAuthoredLinks, Boolean properties which default to true and control the inclusion of these types.

I think I'll break here for now. Next time we'll look at the Navigation Settings page which you can use to easily configuration navigation on a per-Web site basis. I'll also make sure to tie up all the loose ends I've unraveled in the course of this post.

Firefox Hacks is ideal for power users who want to take full advantage of Firefox from Mozilla, the next-generation web browser that is rapidly subverting Internet Explorer's once-dominant audience. It's also the first book that specifically dedicates itself to this technology. Firefox is winning such widespread approval for a number of reasons, including the fact that it lets users browse faster and more efficiently. Perhaps its most appealing strength, though, is its increased security something that is covered in great detail in Firefox Hacks. Clearly the web browser of the future, Firefox includes most of the features that browser users are familiar with, along with several new features, such as a bookmarks toolbar and tabbed pages that allow users to quickly switch among several web sites. Firefox Hacks offers all the valuable tips and tools you need to maximize the effectiveness of this hot web application. It's all covered, including how to customize its deployment, appearance, features, and functionality. You'll even learn how to install, use, and alter extensions and plug-ins. Aimed at clever people who may or may not be capable of basic programming tasks, this convenient resource describes 100 techniques for 100 strategies that effectively exploit Firefox. Or, put another way, readers of every stripe will find all the user- friendly tips, tools, and tricks they need to make a productive switch to Firefox. With Firefox Hacks, a superior and safer browsing experience is truly only pages away. The latest in O'Reilly's celebrated Hacks series, Firefox Hacks smartly complements other web- application titles such as Google Hacks and PayPal Hacks.

Book Info:
Published in 2005
Published by O'Reilly
Author Nigel McFarlane
ISBN 0596009283 Size 2.88MB

Alternative Link:

Link2
http://www.eazyupload.net/download/HpKBHmhv/0596009283.zip.htm


Name: Wrox.SQL.Functions.Programmers.Reference.2005
Type: Database Education
Size: 2765 KB
Document Type: PDF
Page Number: 549 Pages
Download Link:
http://rapidshare.com/files/91213667...rence.2005.rar
Rate: 9 Star / 10 Star
--------------------------
Name: grafix magazine vol1
Keywords: Magazine, Graphic, Grafix, Computer graphic, ...
Type: Magazine (Full Color)
Size: 3698 KB
Document Type: PDF
Download Link: http://rapidshare.com/files/89649681...azine-vol1.pdf
Rate: 8.5 Star / 10 Star
--------------------------
Name: Beginning SQL
Type: Education
Author: Paul Wilton and John W. Colby
Publisher: Wrox - Wiley Publishing Inc.
Size: 3627 KB
Document Type: PDF in rar
Page Number: 522 Pages
Download Link: http://rapidshare.com/files/90343762...erver.2005.rar
Rate: 9 Star / 10 Star
--------------------------
Name: Build A Website That Sells.pdf
Description: [ 10 EASY WAYS To Grow Your Website ]
Publisher: MSPress
Download Link: http://rapidshare.com/files/89650264...That_Sells.pdf
--------------------------
Name: Beginning ASP.NET 2.0 in C# 2005
Type: Computer EBook - C#
Size: 23210 KB
Document Type: PDF in rar upload
Download Link: http://rapidshare.com/files/81485048...in_C__2005.rar
Rate: 9.5 Star / 10 Star
--------------------------
Name: SAMS Teach Yourself Ajax in 10 Minutes
By Phil Ballard
Publisher: Sams
Pub Date: April 28, 2006
Print ISBN-10: 0-672-32868-2
Print ISBN-13: 978-0-672-32868-8
Pages: 240
Type: Computer Education Web High Technology
Size: 2561 KB
Document Type: CHM
Download Link: http://rapidshare.com/files/91452902...es_Apr2006.chm
Rate: 9 Star / 10 Star
--------------------------
Name: Professional.Ajax
Type: Ajax Technology Guide
Size: 3713 KB
Document Type: CHM
byNicholas C. Zakas, Jeremy McPeakandJoe Fawcett
Wrox Press 2006 (432 pages)
ISBN:0471777781
Written for experienced web developers, this authoritative guide shows how to combine tried-and-true CSS, XML, and JavaScript technologies into Ajax to provide web developers with the ability to create more sophisticated and responsive user interfaces.
Download Link:
http://rapidshare.com/files/91722395...ional.Ajax.chm
Rate: 10 Star / 10 Star
--------------------------
Name: Maximum Accessibility Making Your Web Site More Usable for Everyone
Type: Web Standard and Implemntation Guide
Size: 6517 KB
Document Type: CHM
Publisher : Addison Wesley
Pub Date : September 13, 2002
ISBN : 0-201-77422-4
Pages : 640
Accessibility is now a legal requirement for all national government Web sites in the U.S., Canada, Australia, and the European Union. Throughout the world, many other organizations-universities, schools, and private companies-are recognizing that accessibility is a moral and business imperative; many are adopting policies aimed at making Web resources accessible to the more than six hundred million people with disabilities worldwide.
Download Link:
http://rapidshare.com/files/91723252...r_Everyone.chm
Rate: 8.5 Star / 10 Star
--------------------------
Name: A First Look at Sql Server 2005 For Developers
Type: SQL Server 2005 Education
Publisher: Addison Wesely
Size: 4897 KB
Document Type: PDF
Page Number: 737 Pages
Download Link:
http://rapidshare.com/files/93184691...erver_2005.pdf
Rate: 9.5 Star / 10 Star
Page Cover:
E Books Downloads - The Ethical Hacking
--------------------------
Name: Professional ASP.NET 2.0 AJAX
Size: 9105 KB
Document Type: CHM
Page Number: 327 Pages
Info:
Professional ASP.NET 2.0 AJAX
byMatt GibbsandDan Wahlin
Wrox Press 2007 (327 pages)
ISBN:9780470109625
Written by Microsoft's lead ASP.NET AJAX developer together with an in-the-field Microsoft MVP, this book guides you through the core of the Microsoft AJAX Library and the controls you will use to leverage AJAX.
Download Link: http://rapidshare.com/files/91724241...T.2.0.AJAX.chm
Rate: 10 Star / 10 Star
Page Cover:
E Books Downloads - The Ethical Hacking
--------------------------
Name: SAMS Teach Yourself Ajax in 10 Minutes
Size: 2560 KB
Document Type: CHM
Page Number: 240 Pages
Info:
SAMS Teach Yourself Ajax in 10 Minutes
By Phil Ballard
Publisher: Sams
Pub Date: April 28, 2006
Print ISBN-10: 0-672-32868-2
Print ISBN-13: 978-0-672-32868-8
Download Link:
http://rapidshare.com/files/91724521...10.Minutes.chm
Rate: 9 Star / 10 Star
Book Cover:
E Books Downloads - The Ethical Hacking
--------------------------
Name: Addison Wesley Professional Essential CSharp 2.0
Size: 4662 KB
Document Type: CHM
Page Number: 768 Pages
Info:
By Mark Michaelis
Publisher: Addison Wesley Professional
Pub Date: July 13, 2006
Print ISBN-10: 0321150775
Print ISBN-13: 978-0-321-15077-6
Download Link:

Code:
http://rapidshare.com/files/91724988/Addison.Wesley.Essential.CSharp.2.0.chm
Rate: 9 Star / 10 Star
Book Cover:
E Books Downloads - The Ethical Hacking
--------------------------
Name: Pro .NET 2.0 Windows Forms and Custom Controls in C#
Size: 31893 KB
Document Type: PDF
Page Number: 1081
Download Link:
http://rapidshare.com/files/91728293....in.CSharp.pdf
Rate: 10 Star / 10 Star ***** 5Star *****
Book Cover:
E Books Downloads - The Ethical Hacking
--------------------------
Name: Microsoft® SQL Server 2005: Changing the Paradigm (SQL Server 2005 Public Beta Edition)
Size: 5539 KB
Document Type: CHM
Page Number: 504 Pages
Info:
By Scalability Experts, Inc.
Publisher: Sams
Pub Date: August 30, 2005
ISBN: 0-672-32778-3
Download Link:
http://rapidshare.com/files/91729688...e.Paradigm.chm
Rate: 8.5 Star / 10 Star
Book Cover:
E Books Downloads - The Ethical Hacking
UPLOADED

FOR ALL THE COMPUTER RELATED E BOOKS
REFER

Credit : Contributers




ftp://202.96.64.144/pub/books/

http://freeonlinedownload.cjb.net/

http://virangar.org/Tutorial/E-Book-Orginal/?N=D

Introduction

Have you ever wanted to be able to control your TV, Hi-Fi, or Video using the IR port on your pocket PC? Here's how to do it.

Background

I recently lost the TV remote for my old Sony TV. In itself that was no problem, as I bought a replacement remote which did the job. However, when the TV lost its colour setting, I had a problem as it could only show pictures in black and white, and the replacement remote didn't have the buttons for colour adjustment. I decided to write a program on my old Jornada 525 Pocket PC to send the correct codes to the TV using the IR port.

There appears to be three main protocols for sending IR codes to devices. Sony uses the 'Pulse Coded' method which entails sending a steam of data containing header bits, '1' bits and '0' bits separated by spaces. These bits modulate a carrier of 40KHz, and are of different lengths, 2200 us for the header, 110 us for a 1 bit and 550 us for a 0 bit. The spaces are 550 us of silence. Most Sony equipment uses 12 bits of data, which is separated into 6 bits of address (the device type) and 6 bits of command. So the data looks like this: hxxxxxxyyyyyy where h is the header bit, xxxxxx is the 6 bits of the command (msb first) and yyyyyy is the 6 bits of address. I won't go into any further details on this, as there are many sources on the internet that describe the protocol and list the codes for the different devices. Some newer Sony equipment use 19 bit codes, and I believe that other manufacturers use the same format that I have described. It should also be possible to write similar classes for devices that use 'Space Coded' or 'Shift Coded' protocols.

I have written a class called CIrPulse using Embedded C++, which encapsulates the functionality to control Sony and compatible devices from a Jornada 525 PC running Windows CE 3.0. It should work with other devices and Operating Systems, but you will need to try it!

Using the code

The CIrPulse class exposes a number of functions which makes sending IR codes as easy as possible. On declaring a CIrPulse class you should call FindIrPort() once. This returns a UINT which represents the port number for the IrDA port, which it gets by looking in the registry. This port number is used in all subsequent calls to open the IrDA port for serial comms.

Collapse
UINT CIrPulse::FindIrPort()
{
// Look into the registry for the IR port number


HKEY hKey = NULL;

if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("Comm\\IrDA"),
0, 0, &hKey) == ERROR_SUCCESS)
{
DWORD dwType = 0;
DWORD dwData = 0;
DWORD dwSize = sizeof(dwData);

if (RegQueryValueEx(hKey, _T("Port"), NULL, &dwType,
(LPBYTE) &dwData, &dwSize) == ERROR_SUCCESS)
{
if (dwType == REG_DWORD && dwSize == sizeof(dwData))
{
RegCloseKey(hKey);

return (UINT) dwData;
}
}

RegCloseKey(hKey);
}

return 0;
}

Having got the port number, you can call the Open(UINT) function, passing the port number received from the call to FindIrPort(). This opens the port and sets the serial parameters, returning true if successful. The port is set to 115200 baud, 8 data bits, 2 stop bits and even parity. A discussion of how the carrier is produced, and why I have used these settings appears later in the article.

Collapse
BOOL CIrPulse::Open(UINT uiPort)
{
ASSERT(uiPort > 0 && uiPort <= 255);

Close();

// Open the IRDA port


CString strPort;
strPort.Format(_T("COM%d:"), uiPort);

m_irPort = CreateFile((LPCTSTR) strPort, GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING, 0, NULL);
if (m_irPort == INVALID_HANDLE_VALUE)
{
return FALSE;
}

// Set the size of input and output buffers


VERIFY(SetupComm(m_irPort, 2048, 2048));

// clear the read and write buffers


VERIFY(PurgeComm(m_irPort, PURGE_TXABORT | PURGE_RXABORT |
PURGE_TXCLEAR | PURGE_RXCLEAR));

// Reinitializes all IRDA port settings


DCB dcb;

dcb.DCBlength = sizeof(DCB);

VERIFY(GetCommState(m_irPort, &dcb));

dcb.BaudRate = CBR_115200;
dcb.fBinary = TRUE;
dcb.fParity = TRUE;
dcb.fOutxCtsFlow = FALSE;
dcb.fOutxDsrFlow = FALSE;
dcb.fDtrControl = DTR_CONTROL_DISABLE;
dcb.fDsrSensitivity = FALSE;
dcb.fTXContinueOnXoff = FALSE;
dcb.fOutX = FALSE;
dcb.fInX = FALSE;
dcb.fErrorChar = FALSE;
dcb.fNull = FALSE;
dcb.fRtsControl = RTS_CONTROL_DISABLE;
dcb.fAbortOnError = FALSE;
dcb.ByteSize = 8;
dcb.Parity = EVENPARITY;
dcb.StopBits = TWOSTOPBITS;


VERIFY(SetCommState(m_irPort, &dcb));

// Set the timeouts for all read and write operations


COMMTIMEOUTS timeouts;

VERIFY(GetCommTimeouts(m_irPort, &timeouts));

timeouts.ReadIntervalTimeout = MAXDWORD;
timeouts.ReadTotalTimeoutMultiplier = 0;
timeouts.ReadTotalTimeoutConstant = 0;
timeouts.WriteTotalTimeoutMultiplier = 0;
timeouts.WriteTotalTimeoutConstant = 0;

VERIFY(SetCommTimeouts(m_irPort, &timeouts));

DWORD dwEvent=EV_TXEMPTY;
SetCommMask(m_irPort,dwEvent);

return TRUE;
}

Call the function SetCodeSize(DWORD) to set the number of bits to transmit (eg 12). This can be done at any time, and only needs to be done once. It remains in effect until a subsequent call to change it.

Finally call SendCode(long) passing the actual code to send.

Collapse
BOOL CIrPulse::SendCode(DWORD lValue)
{

DWORD dwCount;
int i=0;

ASSERT(iDataLength>0);


//purge the transmit buffer

VERIFY(PurgeComm(m_irPort, PURGE_TXABORT | PURGE_RXABORT |
PURGE_TXCLEAR | PURGE_RXCLEAR));

// send the code 6 times for each button press

for(int x=0;x<6;x++) {
MakeStream(lValue); //send the code

dwCount=GetTickCount();
while(GetTickCount()<dwCount+26) //delay for 26 ms

i++;

}



return true;
}

Note that this function calls another function MakeStream(long) 6 times, pausing for 26ms between each call. I have found that the code has to be sent a number of times for the receiving device to respond, presumably to prevent spurious activation. The delay of 26ms is necessary for the receiving device to register the code, before the next one appears.

The function MakeStream(long) writes the stream of bytes to the IrPort, and ensures that the correct length of packet is sent depending on whether a start bit, '1' bit or '0' bit is to be sent. The buffer containing the bytes of data (0xdb) is in the form of a ByteArray.

The function Close() naturally enough closes the IrPort after use.

The function works fine on my Jornada, but see the discussion below to see what changes you might have to make.

Collapse
 BOOL CIrPulse::MakeStream(DWORD lValue) {
DWORD dwStreamLength;


//make the start pulse

dwStreamLength=iHPulse/charWidth;
ASSERT(Write((const char *)bPulseStream.GetData(),
dwStreamLength)==dwStreamLength);
// ********************************************

// ***** The Delay before the next pulse goes here

// ********************************************


//loop through the bits in the Code sending the pulse

for(int i=0;i<iDataLength;i++) {
if(lValue & 1) {
//make the 1 pulse

dwStreamLength=i1Pulse/charWidth;
ASSERT(Write((const char *)bPulseStream.GetData(),
dwStreamLength)==dwStreamLength);
// ********************************************

// ***** The Delay before the next pulse goes here

// ********************************************

}
else {
//make the 0 pulse

dwStreamLength=i0Pulse/charWidth;
ASSERT(Write((const char *)bPulseStream.GetData(),
dwStreamLength)==dwStreamLength);
// ********************************************

// ***** The Delay before the next pulse goes here

// ********************************************


}
lValue >>= 1;
}
return TRUE;
}

I have included a simple application, which uses CIrPulse to create a remote control for a Sony TV. This has the basic functions of channel selection, volume control and On/Off.

Points of Interest

Because the CIrPort class uses a serial comms interface to the IR port, a 40KHz carrier has to be generated by sending appropriate characters out of the serial port. Fortunately if we send the character 0xdb at 115200 baud with 8 data bits, 2 stop bits and even parity, this has the effect of producing a 38.4 KHz carrier which is close enough. All my Sony equipment accept this without problems.

The biggest problem is how to achieve the periods of silence which separate each pulse. It is not possible to produce pure silence out of the serial port, as even if you send a 0x0 character, you still get pulses on the IR due to start and stop bits. I experimented with sending different characters on the assumption that if you can send a carrier at frequencies other than 40KHz, this might fool the device into accepting this as a silence. This has the advantage in that you can produce one byteArray containing the data for the entire code, ensuring that the timing is accurate. The results however were not consistent, and I rejected this method in favour of pausing between sending the groups of 0xdb characters out of the serial port. Because the delay needed is in the order of 550 us, there is no way (that I found) of consistently achieving this delay which is independent of processor speed. On my Jornada I didn't need to create a delay at all, as each call to the Write function seemed to take the correct amount of time. Anyway, I am afraid to say that you may have to fiddle about to create a delay that works with your Pocket PC. If any one can work out a fool-proof way of achieving the correct delay for any device, please let me know!

Introduction

This article describes the implementation of a Windows Mobile desktop remote controller. With this application, you will be able to remotely control your Windows Mobile device by using the mouse and keyboard.

Background

The code in this article builds on my previous article: A Remote Windows Mobile Screen Grabber. Instead of blocking RAPI calls, this application implements a streamed RAPI server that allows the desktop application to have a permanent connection to the device. Also, in this code, I have dropped the GAPI and DirectDraw screen grabbing techniques, and used a simpler GDI based screen grabbing technique. To improve communications performance, the code uses the ZLIB compression library on both ends.

Desktop code

Please refer to the CeRemoteClient directory on the distribution Zip file for the desktop project.

The bulk of the desktop code is on the CeRemoteClientView.h file. This is in fact a WTL 8.0 frame child window that implements all the desktop client features.

Device connection is performed via the public Connect() and Disconnect() methods. These are called by the menu and toolbar handlers in the frame window class implementation (MainFrm.h). When the desktop successfully connects to the device, a 200 millisecond timer is created to poll the device for the compressed screen bitmap.

The device screen is retrieved by the private GetScreen() method. It first kills the timer, and sends a message to the device server requesting the current screen:

KillTimer(SCREEN_TIMER);
hr = Write(RCM_GETSCREEN);

The device server returns a message containing the same message code, the compressed size of the screen buffer, its expanded size, and the compressed byte stream. After reading the three first DWORDs, the code makes sure there is enough room on both the compressed and expanded buffers, and then reads the compressed byte stream:

// Read the compressed buffer
hr = m_pStream->Read(m_pZipBuf, cbZipBuf, &ulRead);

If all is well, the compressed buffer is decompressed and the resulting DIB is queried for the bitmap dimensions. If the dimensions are different than the last time, then it is very likely that the device screen was rotated, so the whole window is invalidated to erase any garbage:

zr = uncompress(m_pScrBuf, &nDestLen, m_pZipBuf, cbZipBuf);
if(zr == Z_OK)
{
DIBINFO* pDibInfo = (DIBINFO*)m_pScrBuf;
BYTE* pBmpData = (BYTE*)(m_pScrBuf + sizeof(DIBINFO));
BOOL bErase = FALSE;

if(m_xDevScr != pDibInfo->bmiHeader.biWidth ||
m_yDevScr != pDibInfo->bmiHeader.biHeight)
{
m_xDevScr = pDibInfo->bmiHeader.biWidth;
m_yDevScr = pDibInfo->bmiHeader.biHeight;

SetScrollSize(m_xDevScr, m_yDevScr);

bErase = TRUE;
}

m_dib.SetBitmap((BITMAPINFO*)pDibInfo, pBmpData);

InvalidateRect(NULL, bErase);
UpdateWindow();
}

After forcing the window to update, the timer is restarted so we can get the next screen.

Sending input

Sending keyboard and mouse input to the device is pretty simple: handle the corresponding window messages, convert their data content to INPUT structures, and send them to the server for processing. Here's the WM_KEYDOWN handler:

LRESULT OnKeyDown(TCHAR vk, UINT cRepeat, UINT flags)
{
HRESULT hr;
INPUT input;

if(!m_bConnected)
return 0;

input.type = INPUT_KEYBOARD;
input.ki.wVk = MapKey(vk);
input.ki.wScan = 0;
input.ki.dwFlags = 0;
input.ki.time = 0;
input.ki.dwExtraInfo = 0;

hr = Write(RCM_SETINPUT);
if(SUCCEEDED(hr))
{
hr = Write(&input, sizeof(input));
if(SUCCEEDED(hr))
GetScreen();
}

return 0;
}

The MapKey() function performs basic key mappings between the desktop and the device keyboards. Use the F1 key for the left function button, and F2 for the right. The F3 and F4 keys naturally map to the phone keys.

Sending mouse actions is similar:

LRESULT OnLButtonDown(UINT Flags, CPoint pt)
{
HRESULT hr;
INPUT input;

if(!m_bConnected)
return 0;

m_bLeftBtn = true;
input.type = INPUT_MOUSE;
input.mi.dwFlags = MOUSEEVENTF_ABSOLUTE |
MOUSEEVENTF_LEFTDOWN;
input.mi.dx = pt.x * 65536 / m_xDevScr;
input.mi.dy = pt.y * 65536 / m_yDevScr;
input.mi.mouseData = 0;
input.mi.time = 0;
input.mi.dwExtraInfo = 0;

hr = Write(RCM_SETINPUT);
if(SUCCEEDED(hr))
{
hr = Write(&input, sizeof(input));
if(SUCCEEDED(hr))
hr = GetScreen();
}

return 0;
}

Note how the mouse screen coordinates are normalized for the device screen. This is a requirement of the SendInput API used on the device server.

Now that I mentioned it, let's take a closer look at the device server code.

Device code

Please refer to the CeRemSrv directory on the distribution Zip file for the device project.

The bulk of the device code is implemented in the CRemoteControl class. All messages sent by the desktop client are processed and dispatched on the executive loop implemented in the Run method.

The device screen is captured by the SendScreen method which has a very similar structure to its desktop counterpart. Note how the device screen is captured so easily:

hDC = GetWindowDC(NULL);

After getting the HDC of the device screen, you can very easily copy it into a bitmap and serialize it to the desktop. There's no need for fancy GAPI or DirectDraw techniques like I used before.

After getting the device screen copied into a DIB, the whole thing is compressed and sent back to the desktop client:

memcpy(m_pScrBuf + i, m_dib.GetBitmapInfo(), sizeof(DIBINFO));
i += sizeof(DIBINFO);

memcpy(m_pScrBuf + i, m_dib.GetDIBits(), m_dib.GetImageSize());
i += m_dib.GetImageSize();

ULONG len = m_cbZipBuf;
int zr = compress(m_pZipBuf, &len, m_pScrBuf, cbNew);

if(zr != Z_OK)
len = 0;

hr = m_pStream->Write(&dwMsg, sizeof(DWORD), &ulWritten);
hr = m_pStream->Write(&len, sizeof(ULONG), &ulWritten);
hr = m_pStream->Write(&cbNew, sizeof(DWORD), &ulWritten);
hr = m_pStream->Write(m_pZipBuf, len, &ulWritten);

Handling input from the desktop is even simpler:

HRESULT CRemoteControl::GetInput()
{
INPUT input;
HRESULT hr;
DWORD dwRead;

hr = m_pStream->Read(&input, sizeof(input), &dwRead);
if(FAILED(hr))
return hr;

if(dwRead != sizeof(input))
return E_FAIL;

SendInput(1, &input, sizeof(input));

return S_OK;
}

A very simple implementation indeed.

Points of interest

There are two interesting things you may like to know: how I simulated the double-click mouse event, and why this code will not work out of the box on WM5 and WM6 devices.

Double-clicks had to be simulated by sending four messages to the device. This happens because the desktop window manager will merge the four mouse events (down - up - down - up) into a single message - WM_LBUTTONDBLCLK. If you look at my code, you will see how this was undone...

On WM5 and WM6 devices, you may have to enable RAPI connectivity in order for the device server DLL to respond to the client. I once wrote a simple device tool to help you with this nasty chore. You can get it here. Copy the EXE to the device and execute it.

History

  • 2008-03-28 - Corrected flickering.
  • 2008-03-17 - First release.

Screenshot

Introduction

This is a simple GPS tracer developed for Window Mobile 2005/2003 on Compact Framework 2.0 SDK. So first of all, you need VisualStudio 2005 and Windows Mobile CE 5 SDK. You can develop it on emulator devices or on a real device. As you can see in that photo, I developed that application on a read device: the great Asus MyPal 636N.

Screenshot

How it works: background

The screenshot above shows that the map generated by this application is very simple. It's only your path, and the application is able to:

  • Read data from any NMEA GPS device
  • Read your position and print it to the screen
  • Load and save your path
  • Zoom in/out
  • Pan on your path
  • Center on the map
  • Run in demo mode with randomly generated data

You can save and load it, but for now you can't edit or add other text. About application setup, it's very simple: you have only to setup your COM port. This port must be that same port where your NMEA device is attached via Bluetooth, IrDA or Integrate. Personally, I have an Asus MyPal 636N device, so I have the GPS device built-in on COM5.

Using the code

It's basically composed of three main actors, similar to a simple MVC pattern:

Form (Control): It's the main form of the application, so it contains the Windows UI (menu, controls...)

Reader (Model): It's the class that works with the GPS device, so it allows reading from serial with a threaded method

Mapper (View): It's the actor that parses the GPS NMEA phrases and draws them on-screen; it also allows the user to zoom and pan the map that contains the path

Form

It initializes the application; as you can see it's able to run on 240px X 320px devices.

        public Form1()
{
InitializeComponent();
m_graphics = this.CreateGraphics();
m_mapper = new Mapper(m_graphics, 0, 30, 240, 300);
m_rTh = new reader(m_port);
m_rTh.dataReceived += new reader.DataReceivedEventHandler(parse);
}

The event registered method is called on the DataReceived event. With m_isDemoMode==True, the application will generate a randoom coordinate.

        public void parse(String readed)
{
if (!m_isDemoMode)
{
m_mapper.parseAndDraw(readed);
}
else
{
Random r = new Random();
String rSecond1 = (int)(r.NextDouble() * 10 - 1) + "" +
(int)(r.NextDouble() * 10 - 1) + "" +
(int)(r.NextDouble() * 10 - 1) + "" +
(int)(r.NextDouble() * 10 - 1);
String rSecond2 = (int)(r.NextDouble() * 10 - 1) + "" +
(int)(r.NextDouble() * 10 - 1) + "" +
(int)(r.NextDouble() * 10 - 1) + "" +
(int)(r.NextDouble() * 10 - 1);
String rPrime1 = (int)(r.NextDouble() * 1 - 1) + "";
String rPrime2 = (int)(r.NextDouble() * 1 - 1) + "";

m_mapper.drawLatLong("434" + rPrime1 + "." + rSecond1,
"0111" + rPrime2 + "." + rSecond2);
}
}

This is the method that starts and stops the Readed thread.

        private void menuItemRunStop_Click(object sender, EventArgs e)
{
if (m_isRunning)
{
m_rTh.stop();
}
else
{
m_rTh.start();
}
menuItemRunStop.Checked = !menuItemRunStop.Checked;
m_isRunning = !m_isRunning;
}

This is the region that solves problem of panning the map on your touchscreen device. You can enable m_mapper.clearAndDraw() and it will clear the screen before it redraws the moved map.

Collapse
        #region Panning

private Point touch;
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (m_mapper != null)
{
m_mapper.moveCenter(touch.X - e.X, touch.Y - e.Y);
m_mapper.draw();//m_mapper.clearAndDraw();

}
touch.X = e.X;
touch.Y = e.Y;
}

private void Form1_MouseDown(object sender, MouseEventArgs e)
{
touch.X = e.X;
touch.Y = e.Y;
}


private void Form1_MouseUp(object sender, MouseEventArgs e)
{
Form1_MouseMove(sender, e);
m_mapper.clearAndDraw();
}

#endregion

Reader

Event exposed to Form on data received:

        public delegate void DataReceivedEventHandler(string data);
public event DataReceivedEventHandler dataReceived;

The thread method that reads on serial port:

        private void methodTh()
{
m_serialPort1.Open();
byte[] buffer= new byte[100];
while (m_run)
{
Thread.Sleep(500);
m_readed = m_serialPort1.ReadLine();
if (m_readed.Length > 0)
{
dataReceived(m_readed);
}
}
}

These methods allow the Form to start and stop the reader thread:

        public void start()
{
if (m_th == null)
{
m_th = new Thread(methodTh);
}
m_run = true;
m_th.Start();
}

public void stop()
{
m_run = false;
Thread.Sleep(500);
m_serialPort1.Close();
if (m_th != null)
{
m_th.Abort();
m_th = null;
}
}

Mapper

Now follow me through the Mapper section. This is a simple method that parses the data read from the Reader. It's called by the Form on a DataReceivedEvent.

Collapse
        public void parseAndDraw(string s)
{
string[] Words = s.Split(',');

m_g.FillRectangle(m_bgBrush, new Rectangle(m_clip.X,
m_clip.Y + 5, m_clip.Width, 15));
m_g.DrawString(s, m_font,m_fontBrush, new RectangleF(m_clip.X ,
m_clip.Y + 5, m_clip.Width, 15));

switch (Words[0])
{
case "$GPRMC":
// $GPRMC,170111,A,4338.5810,N,07015.1010,W,000.0,

// 360.0,060199,017.5,W*73

// RMC - Recommended minimum specific GPS/Transit data


if (Words[3].Length > 0 && Words[5].Length > 0)
{
drawLatLong(Words[3], Words[5]);
}
break;
case "$GPGSV":
// $GPGSV,2,1,08,03,17,171,42,06,21,047,44,14,

// 28,251,45,16,25,292,44*71

// GSV - Satellites in view

break;
case "$GPGSA":
// $GPGGA,170111,4338.581,N,07015.101,W,1,

00,2.0,1.1,M,-31.8,M,,*71
//GSA - GPS dilution of precision and active satellites

break;
default:
break;
}
}

The drawLatLong() method allows conversion of the latitude and longitude data. In the example, latitude:43 38.5810 will be converted to 162710 using the formula 43*360+34*60+5810. At the end of parsing and conversion, it will add the data to the private List<POINT> m_points; and later it will call the method draw(). This method will draw all of the lines that were not drawn before. Obviously, on pan or application start this method will draw all points.

Collapse
public void drawLatLong(string latitude, string longitude)
{

Point aPoint = new Point();

aPoint.X =
(Convert.ToInt32(latitude.Substring(latitude.Length - 4, 4)));
aPoint.Y =
(Convert.ToInt32(longitude.Substring(longitude.Length - 4, 4)));

aPoint.X +=
(Convert.ToInt32(latitude.Substring(latitude.Length - 7, 2))) * 60;
aPoint.Y +=
(Convert.ToInt32(longitude.Substring(longitude.Length - 7, 2))) * 60;

aPoint.X +=
(Convert.ToInt32(latitude.Substring(latitude.Length - 9, 2))) * 3600;
aPoint.Y +=
(Convert.ToInt32(longitude.Substring(longitude.Length - 9, 2))) * 3600;

m_points.Add(aPoint);
draw();
}

public void draw()
{
float xTo = 0;
float xFrom = 0;
float yTo = 0;
float yFrom = 0;

for (int i = m_drawded; i < m_points.Count; i++)
{
xTo = (m_points[i].X - m_points[0].X) / m_scale + m_center.X;
xFrom = (m_points[i - 1].X -
m_points[0].X) / m_scale + m_center.X;
yTo = (m_points[i].Y - m_points[0].Y) / m_scale + m_center.Y;
yFrom = (m_points[i - 1].Y - m_points[0].Y) /
m_scale + m_center.Y;
m_g.DrawLine(m_linePen, (int)xTo, (int)yTo,
(int)xFrom, (int)yFrom);
m_g.DrawEllipse(m_pointPen,
new Rectangle((int)xFrom - 2,
(int)yFrom - 2, 4, 4));
}

m_g.DrawEllipse(m_lastPointPen,
new Rectangle((int)xTo - 2, (int)yTo - 2, 4, 4));
m_drawded++;
}

At the end, you can see the loadPath(...) and savePath methods. These methods are called by the form that asks the user the filename/location from the windows form dialog m_mapper.loatPath(openFileDialog1.FileName); m_mapper.savePath(saveFileDialog1.FileName);. So this simply loads/saves the private List<POINT> m_points; from/to a file.

Collapse
        public void loatPath(String filename)
{
StreamReader sr = new StreamReader(filename);

m_points.Clear();
int n = 0;
Point p = new Point();
while (!sr.EndOfStream)
{

String readed = sr.ReadLine();
p.X = Convert.ToInt32(readed);
readed = sr.ReadLine();
p.Y = Convert.ToInt32(readed);
m_points.Add(p);
n++;
}
m_drawded = 1;
sr.Close();
clearAndDraw();
}

public void savePath(String filename)
{
StreamWriter sw = new StreamWriter(filename);

foreach (Point p in m_points)
{
sw.WriteLine(Convert.ToString(p.X));
sw.WriteLine(Convert.ToString(p.Y));
}

sw.Flush();
sw.Close();
}

Points of interest

I know that this is a simple project for a skilled developer, but I want to demonstrate that the device development is very simple with VisualStudio2005 and CompactFramework 2.0. It's my first project on the great CodeProject website, so please contact me if you have any doubts or proposals. I'm always available for collaboration.

History

  • Start of development: 8/7/2006
  • First release: 8/9/2006
  • Second release : 8/10/2006
    • Added Center function
    • Now demo mode run without GPS or any serial port
    • Some and various fix
  • Article edited and posted to the main CodeProject.com article base: 5/22/2007

Contents

  • Introduction
  • Background
    • The protocol
      • Calculating the checksum
      • Extracting data from the messages
        • Track points
        • Tracks
  • Using the code
    • Problems implementing the protocol
    • Splitting the messages
  • Getting the connection up and running
  • Points of interest
  • History

Introduction

Recently, I've been exposed to the whole world there is to the ever smaller and lighter electronic-equipments. I've always been interested in cutting-edge technology and somehow I got involved in an aeromodelism group where it was decided that the best (and simplest) solution for measuring flight data was to use a GPS device. No doubt we chose the world leading brand Garmin, of all the features available in their devices, the one that was important to us was track recording. As the sole Windows application programmer in the group, I was requested to create software that would transfer data between the GPS unit and a Pocket PC so that adjustments could be made instantly based on the data collected which would be analyzed in graphs.

Background

The protocol

Garmin uses a proprietary format that is not complicated, but let's cover its basics here as there aren't many thorough resources on the internet.

Information transferred to and from the GPS is divided into packets, which we'll refer to as messages. Each message starts with a hexadecimal 0x10 code and ends with two bytes 0x10 and 0x03. The information itself is found between these starting and finishing bytes. The second byte refers to the kind of information that is being transmitted, i.e. the message ID. Next, we have a byte which represents the number of bytes that are to be sent starting from the following byte up to the last one, just before a checksum byte and the trailing 0x10 0x03. But if it were as simple as that, the protocol would not be totally robust. Let's imagine for example that the first information-byte, which is the 4th, has the bits set to 0x10 and the following one 0x03. A simple interpreter would get it as a message-finalizer, thus wrongly recognizing a message-escape sequence and causing an error. In order to remove such a possibility, Garmin chose to repeat the corresponding byte whenever 0x10 should be sent, as long as it's an information-byte, so that in the above case we would receive 0x10 0x10 0x03 accounting for only two bytes, thus removing any possible errors. Repeated 0x10 accounts for only one byte as far as the third-byte (count byte) is concerned. Let's see some examples of messages. Move your mouse over the bytes to see their descriptions:

Pocket PC: 10 0A 02 06 00 EE 10 03 - Ask for tracks, the first message sent.
GPS: 10 06 02 0A 00 EE 10 03 - Reply to the previous message, means OK.
GPS: 10 1B 02 05 00 DE 10 03 - Number of records to be sent next: 5.
Pocket PC: 10 06 02 22 00 D6 10 03 - Ask for the next record.
GPS: 10 63 0D 01 FF 41 43 54 49 56 45 20 4C 4F 47 00 D2 10 03 - Track name: ACTIVE LOG.
Pocket PC: 10 06 02 22 00 D6 10 03 - Ask for the next record.
GPS: 10 22 18 01 02 03 04 05 06 07 08 09 10 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 DE 10 03 - Point in track.
Pocket PC: 10 06 02 22 00 D6 10 03 - Ask for the next record.
GPS: 10 0C 02 06 22 CA 10 03 - Track EOF.

Calculating the checksum

In order to calculate the checksum of the message we first sum up all the information-bytes and get the least significant byte of the result by applying an AND (&) operation. Then, we invert the bits by calling XOR (^) 0xff and then we just add 1. The code for doing this is shown below:

private bool CheckSum(System.Collections.ArrayList command,
bool errorDetails)
{
int res=0;
int orig=(byte)command[command.Count-3];

for(int i= 1;ibyte)command[i];
}
res &= 0xff;
res ^= 0xff;
res+=1;
bool retval=(byte)(res) == (byte)orig;
if(!retval && errorDetails)
{
System.Windows.Forms.MessageBox.Show(
"Received message:\n" +
ToHEXstring(command) + "\n\n" +
"Received checksum: " + orig.ToString() +
"\nCalculated checksum:" + res.ToString(),
"Error details",
System.Windows.Forms.MessageBoxButtons.OK,
System.Windows.Forms.MessageBoxIcon.Asterisk,
System.Windows.Forms.MessageBoxDefaultButton.Button1);
}
return ( retval );
}

Extracting data from the messages

Track points

For each point data sent by the GPS, there is the latitude, longitude, time, altitude and whether, it is a new segment within the same track. Each of the first four fields is composed of 4 bytes, in a way that the most significant one is the one to the right. Next we'll discuss how to obtain data values from the bytes.

Coordinates

Both the latitude and longitude are represented by an int32 ranging from -2147483648 to +2147483647 that should be translated into numbers from -90 to +90 for latitude and from -180 to +180 for longitude. The mathematical formulae for both are the same and are shown in the extract below which comprises of all the parsing of a track point message.

Time

The time is the simplest to retrieve from the message: we just read the four corresponding bytes and store them.

Altitude

The altitude is not easy to calculate from within the .NET Compact Framework. This field is transmitted as a float32 and we must retrieve a variable of that type based on the 4 bytes. This complex routine, which I'll not go into detail here, is also shown in the extract below.

New segment flag

Once I discovered which byte to check for such information, getting its data was fairly trivial.

Retrieving data from a track point message
latitude=( ((byte)command[6]<<24 class="code-keyword">byte)command[5]<<16 class="code-keyword">byte)command[4]<<8 class="code-keyword">byte)command[3])
* ( 180.0 / 2147483648.0) );
longitude=( ((byte)command[10]<<24 class="code-keyword">byte)command[9]<<16 class="code-keyword">byte)command[8]<<8 class="code-keyword">byte)command[7])
* ( 180.0 / 2147483648.0 ) );
time=(uint)((byte)command[14]<<24 class="code-keyword">byte)command[13]<<16 class="code-keyword">byte)command[12]<<8 class="code-keyword">byte)command[11]);
if((byte)command[23]==1)
isNewSegment=true;
else
isNewSegment=false;

/* Formula used to get the float represented by 4 bytes */
int h=(byte)command[18]<<24 class="code-keyword">byte)command[17]<<16 class="code-keyword">byte)command[16]<<8 class="code-keyword">byte)command[15];
int exp=(h & 0x7f800000)/(2<<22); class="code-keyword">int frac=h & 0x7fffff;
height=1+(float)frac/((float)(2<<22));>

Tracks

For every new track in the GPS database a message is sent containing the name of a new track and its ID, as seen earlier in the message examples. Next is a snippet from the code that is used to get the track name:

name=""
for(int i=5;i<=(byte)command[2]+1;i++)
name+=Convert.ToChar((byte)command[i]).ToString();

This is just an outline of what the protocol seems to be according to my analysis. For more information on other types of data that can be transferred, you may refer to this.

Having the protocol interpreter done, the next step was to build the data analysis tools, which was not as easy as expected due to the limitations found while comparing the .NET Framework to the .NET Compact Framework. The main feature usually used in graphing applications is vector graphics, achieved with the aid of matrix-transformations such as scale and translation. As a consequence, all the drawing routines had to be created from scratch.

For serial communications, I grabbed routines from Microsoft and the progress bar used to indicate the transfer progress from a GPS unit was taken from the OpenNETCF Smart Device Extensions project, which adds a fancy gradient background to the control.

Using the code

Three important classes used for communication between a Pocket PC and a Garmin GPS unit are included in the source code: Garmin, Track and TrackPoint. The first one is responsible for communicating with the unit and storing the information received into tracks of type Track, each made of points of type TrackPoint, and stored by each track as ArrayLists.

Making the Garmin class communicate with the GPS seemed to be trivial at first, but came out to be much more complex than expected.

Problems implementing the protocol

I tried two methods of communication before getting to the one that I am now using, which hasn't failed even once throughout my tests with an iPaq H2215 device. However, when I tried it on an older model also from HP, the Jornada 548, I couldn't get it to work, probably because of some difference in the hardware implementation of the serial port. Next, I will describe each of the methods that sometimes worked but were not stable and their problems, and also the reliable one that is currently in use:

  1. Parsing as data was received:

    The first one I tried consisted of parsing the data as it was received from the GPS and creating Tracks and TrackPoints according to what was received. The problem with this was as the serial port is asynchronous, I couldn't find out when the message was over while still not skipping any bytes that are the first ones from the next message unless I wrote a complex routine that would do the trick. After adding some delays to make sure that all the data had come and still not getting the expected results, I decided to try another method.

  2. Parsing as data was received and flooding the GPS with send next commands:

    By using this method, whenever the data came in, a send next command was sent to the GPS so as to avoid the problems created by using the artificial delays of the previous method. This method never got to work due to the bytes being skipped.

  3. The currently-in-use working method:

    In this method, I mix the second one with a new concept to make the code reliable. Whenever data is received it is added to an ArrayList and, when an EOF (End of File) is received a function is called to split the messages from the array of bytes stored in the ArrayList and then the Track and its TrackPoints are added. By using this method, the saving and loading of files was made much easier, as the process of loading data from the GPS or from a file would be the same. To save data I need to just copy the bytes in the ArrayList to the storage memory and to load it, do just the reverse and call the function that splits the messages accordingly.

Splitting the messages

Creating a code that would flawlessly break the bytes at every message-end took many hours until I got the idea of an algorithm that would do the trick. As mentioned earlier, when an information-byte has a value of 0x10, it is repeated. So, if we count the number of adjacent 0x10 present in the actual message we'll always get an even number as there will never be a single 0x10, they come in pairs. However, if there's an ending 0x10 byte we'll get an odd number because the ending-byte is always sent on its own. That's it! Breaking the messages was as simple as counting adjacent 0x10 and checking the result. If it is even we should go on. Otherwise, we have found a message break:

Collapse
private static System.Collections.ArrayList SplitBytes(
System.Collections.ArrayList arrayListBytes)
{
System.Collections.ArrayList retval=
new System.Collections.ArrayList(10);
System.Collections.ArrayList tempBytes;
int count=0;

for(int i= 0;iif((byte)arrayListBytes[i]==0x10)
{ /* First byte in a message */
tempBytes=new System.Collections.ArrayList(8);
tempBytes.Add(arrayListBytes[i]);
for(int x=i+1;;x++)
{
i=x;
tempBytes.Add(arrayListBytes[x]);
if((byte)arrayListBytes[x]==0x10)
++count;
else if((byte)arrayListBytes[x]==0x03)
{
if(count%2==1) /* If found an odd number of
* 0x10 followed by 0x03 we
* have to split the message
* here */

break;
}
else
count=0;
}
retval.Add(tempBytes);
}
}
return retval;
}

Getting the connection up and running

In order to connect your Pocket PC to a GPS unit software-wise you just need to create a variable of type Garmin and call its .GetTracks.

Apart from the software, you'll have to establish a real physical connection between the device's serial port and the GPS. For this, I personally recommend Pfranc plugs. I got mine here in Brazil with no problems and they work just great. On the other end of the cable you'll have the Pocket PC. In my case, I had an iPaq which comes with a power adapter. I used this adapter and added some extra wires to it so that it became a charging and serial port connector. Below is an image showing the HP iPaq universal 22-pin connector diagram that shows the wires to connect to the GPS. If you need assistance in creating the cables feel free to e-mail me, but keep in mind that the connections you make on your own could void your warranty and damage your equipment. I got mine to work with no problems at all, but there is a possibility of damaging both the devices. Do whatever you do at your own risk.

Solder-side view of the iPaq power adapter and the pins to solder.

eTrex Pfranc plug, frontal view from the latch.

This is the result of the modifications made to the original plugs.

Points of interest

One interesting feature added to the sample application is the ability to save a spreadsheet file which can be read by most of the spreadsheet editors available in the market. It saves files using the known CSV (Comma Separated Values) format. However, while implementing the save function, I discovered a problem with the CSV format which I couldn't overcome: The decimal-separator character seems to depend on the current system's regional settings, and as a consequence we can't guarantee that a saved file will be read properly by a configuration. Their might be some way to elegantly fix this problem, but the way I figured out works satisfactorily: I use Excel formulae to overcome this issue. Non-integer numbers are multiplied by 1010 and Excel is made to calculate the result of the division of such a number by 1010. This way the decimal separator will always be displayed according to each system's configuration and still will be made possible for reading by whichever configuration. Let's see an example of the formula we use for the altitude measurement in Excel: =7749426479104/10000000000 which accounts for approximately 774.9426 meters.

History

  • 12.03.2005: version 1.06
    • First release.
  • 12.10.2005: version 1.07
    • Fixed an issue in Plotter in which track segments made of only one point would make the program crash.