Speaking Garmin






4.76/5 (24投票s)
介绍让 Pocket PC 和 Garmin GPS 能够用各自的语言进行通信,并将计算出的数据以图形化方式显示所需的基础知识。
目录
引言
最近,我接触到了越来越小巧轻便的电子设备。我一直对尖端技术感兴趣,不知怎么就加入了一个航模小组,他们决定测量飞行数据的最佳(也是最简单)解决方案是使用 GPS 设备。毫无疑问,我们选择了世界领先品牌 Garmin,在其设备提供的众多功能中,对我们重要的是轨迹记录。作为小组中唯一的 Windows 应用程序程序员,我被要求创建软件,在 GPS 设备和 Pocket PC 之间传输数据,以便可以根据收集到的数据即时进行调整,并在图表中进行分析。
背景
协议
Garmin 使用一种专有的格式,并不复杂,但这里有一些关于它的基础知识,因为互联网上关于它的详尽资源并不多。
与 GPS 之间传输的信息被分成数据包,我们称之为消息。每条消息都以十六进制代码 0x10 开始,以两个字节 0x10 和 0x03 结束。信息本身位于这些开始和结束字节之间。第二个字节是指正在传输的信息的类型,即消息 ID。接下来,我们有一个字节,表示从下一个字节到校验和字节和尾随的 0x10 0x03 之前的最后一个字节要发送的字节数。但是,如果仅此而已,协议就不会完全健壮。例如,我们设想第四个信息字节的值为 0x10,而下一个字节为 0x03。一个简单的解释器会将其识别为消息结束符,从而错误地识别出消息转义序列并导致错误。为了消除这种可能性,Garmin 选择在信息字节应发送 0x10 时重复相应的字节,这样在上面的情况下,我们将收到 0x10 0x10 0x03,只占两个字节,从而消除了任何可能的错误。重复的 0x10 在计数字节(第三个字节)方面仅算一个字节。我们来看一些消息的例子。将鼠标悬停在字节上可查看其描述
Pocket PC: 10 0A 02 06 00 EE 10 03 - 请求轨迹,发送的第一条消息。
GPS: 10 06 02 0A 00 EE 10 03 - 对上一条消息的回复,表示 OK。
GPS: 10 1B 02 05 00 DE 10 03 - 接下来的记录数:5。
Pocket PC: 10 06 02 22 00 D6 10 03 - 请求下一条记录。
GPS: 10 63 0D 01 FF 41 43 54 49 56 45 20 4C 4F 47 00 D2 10 03 - 轨迹名称:ACTIVE LOG。
Pocket PC: 10 06 02 22 00 D6 10 03 - 请求下一条记录。
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 - 轨迹中的一个点。
Pocket PC: 10 06 02 22 00 D6 10 03 - 请求下一条记录。
GPS: 10 0C 02 06 22 CA 10 03 - 轨迹 EOF。
计算校验和
要计算消息的校验和,我们首先对所有信息字节求和,然后通过 AND (&) 运算得到结果的最低有效字节。然后,我们通过调用 XOR (^) 0xff 来反转位,最后加 1。下面显示了执行此操作的代码。
private bool CheckSum(System.Collections.ArrayList command,
bool errorDetails)
{
int res=0;
int orig=(byte)command[command.Count-3];
for(int i= 1;i<command.Count-3;i++)
{
res+=(byte)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 );
}
从消息中提取数据轨迹点
对于 GPS 发送的每个点数据,都有纬度、经度、时间、高度以及它是否是同一轨迹中的一个新片段。前四个字段中的每个字段都由 4 个字节组成,其中最高有效字节位于右侧。接下来,我们将讨论如何从字节中获取数据值。
坐标
纬度和经度都由一个 int32
表示,范围从 -2147483648 到 +2147483647,应分别转换为 -90 到 +90(纬度)和 -180 到 +180(经度)。两者都使用相同的数学公式,下面提取的代码包含了对轨迹点消息的所有解析。
时间
时间检索起来最简单:我们只需读取相应的四个字节并存储它们。
高度
.NET Compact Framework 中计算高度并不容易。此字段以 float32
的形式传输,我们必须根据这 4 个字节检索一个该类型的变量。这个复杂的例程,我不会在此详细介绍,也包含在下面的提取中。
新片段标志
一旦我发现了需要检查哪个字节来获取此信息,获取其数据就相当简单了。
从轨迹点消息中检索数据
latitude=( ((byte)command[6]<<24 |
(byte)command[5]<<16 |
(byte)command[4]<<8 |
(byte)command[3])
* ( 180.0 / 2147483648.0) );
longitude=( ((byte)command[10]<<24 |
(byte)command[9]<<16 |
(byte)command[8]<<8 |
(byte)command[7])
* ( 180.0 / 2147483648.0 ) );
time=(uint)((byte)command[14]<<24 |
(byte)command[13]<<16 |
(byte)command[12]<<8 |
(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 |
(byte)command[17]<<16 |
(byte)command[16]<<8 |
(byte)command[15];
int exp=(h & 0x7f800000)/(2<<22);
int frac=h & 0x7fffff;
height=1+(float)frac/((float)(2<<22));
height *= 2<<(exp-128);
轨迹
对于 GPS 数据库中的每个新轨迹,都会发送一条消息,其中包含新轨迹的名称及其 ID,如前面的消息示例所示。下面是从用于获取轨迹名称的代码片段。
name=""
for(int i=5;i<=(byte)command[2]+1;i++)
name+=Convert.ToChar((byte)command[i]).ToString();
这只是根据我的分析,协议可能的样子的大纲。有关可以传输的其他类型数据的更多信息,请参阅 此处。
完成了协议解释器之后,下一步是构建数据分析工具,这并不像预期的那么容易,因为在比较 .NET Framework 与 .NET Compact Framework 时发现了限制。图形应用程序中常用的主要功能是矢量图形,它借助矩阵变换(如缩放和平移)来实现。因此,所有绘图例程都必须从头开始创建。
对于串行通信,我从 Microsoft 获取了例程,用于指示 GPS 设备传输进度的进度条则取自 OpenNETCF Smart Device Extensions 项目,该项目为控件添加了华丽的渐变背景。
使用代码
源代码中包含了用于 Pocket PC 和 Garmin GPS 设备之间通信的三个重要类:Garmin
、Track
和 TrackPoint
。第一个类负责与设备通信,并将收到的信息存储到 Track
类型的轨迹中,每个轨迹由 TrackPoint
类型的点组成,并由每个轨迹作为 ArrayList
存储。
起初,让 Garmin
类与 GPS 通信似乎很简单,但事实证明比预期的要复杂得多。
实现协议时遇到的问题
在找到我现在使用的方法之前,我尝试了两种通信方法,这种方法在我使用 iPaq H2215 设备进行的测试中从未失败过。但是,当我在另一款较旧的 HP 型号 Jornada 548 上尝试时,我无法使其工作,可能是因为串行端口的硬件实现存在某些差异。接下来,我将描述每种有时有效但不稳定以及目前使用的工作正常的方法及其问题。
- 收到数据后进行解析
我尝试的第一个方法是解析从 GPS 收到的数据,并根据接收到的内容创建
Track
和TrackPoint
。问题在于串行端口是异步的,我无法在不跳过作为下一条消息起始字节的任何字节的情况下,弄清楚消息何时结束,除非我编写一个复杂的例程来完成这项工作。在添加了一些延迟以确保所有数据都已到达,但仍未获得预期结果后,我决定尝试另一种方法。 - 收到数据后进行解析并且向 GPS 泛滥“发送下一条”命令
通过使用这种方法,每当数据进来时,就会向 GPS 发送一个“发送下一条”命令,以避免使用上一种方法中的人工延迟所带来的问题。由于字节被跳过,这种方法从未奏效。
- 当前正在使用的工作方法
在此方法中,我将第二种方法与一个新概念结合起来,以使代码可靠。每当收到数据时,都会将其添加到
ArrayList
中,并且在收到 EOF(文件结束)时,会调用一个函数来从ArrayList
中存储的字节数组中拆分消息,然后添加Track
及其TrackPoint
。通过使用这种方法,文件的保存和加载变得更加容易,因为从 GPS 或文件加载数据的过程将是相同的。要保存数据,我只需要将ArrayList
中的字节复制到存储内存中,要加载它,则执行相反的操作,并调用相应地拆分消息的函数。
拆分消息
创建能够完美地在每个消息结尾处拆分字节的代码花费了许多小时,直到我想到一个可以完成这项工作的算法。如前所述,当信息字节的值为 0x10 时,它会被重复。因此,如果我们计算实际消息中连续出现的 0x10 的数量,我们将始终得到一个偶数,因为永远不会有单个 0x10,它们成对出现。但是,如果存在结尾的 0x10 字节,我们将得到一个奇数,因为结尾字节始终单独发送。就是这样!拆分消息就像计算连续的 0x10 并检查结果一样简单。如果它是偶数,我们应该继续。否则,我们就找到了一个消息分隔符。
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;i<arrayListBytes.Count;i++)
{
if((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;
}
建立连接并使其正常工作
要通过软件将 Pocket PC 连接到 GPS 设备,您只需创建一个 Garmin
类型的变量并调用其 .GetTracks
方法。
除了软件,您还需要在设备的串行端口和 GPS 之间建立实际的物理连接。为此,我个人推荐 Pfranc 插头。我在巴西这里毫无问题地获得了我的插头,它们工作得非常好。电缆的另一端将是 Pocket PC。在我的例子中,我有一个 iPaq,它带有一个电源适配器。我使用了这个适配器并添加了一些额外的电线,使其成为一个充电器和串行端口连接器。下面是一张显示 HP iPaq 通用 22 针连接器图的图片,其中显示了要连接到 GPS 的电线。如果您在创建电缆方面需要帮助,请随时给我发送电子邮件,但请记住,您自己进行的连接可能会使您的保修失效并损坏您的设备。我自己的设备工作得很好,但有可能损坏这两个设备。您所做的一切都自行承担风险。
iPaq 电源适配器和要焊接的引脚的焊盘侧视图。
eTrex Pfranc 插头,从闩锁处看正面。
这是对原装插头进行修改后的结果。
关注点
示例应用程序中添加的一个有趣功能是能够保存电子表格文件,该文件可以被市场上大多数电子表格编辑器读取。它使用已知的 CSV(逗号分隔值)格式保存文件。但是,在实现保存功能时,我发现 CSV 格式存在一个我无法克服的问题:小数分隔符似乎取决于当前系统的区域设置,因此我们无法保证保存的文件能被某个配置正确读取。可能有一些方法可以优雅地解决这个问题,但我找到的方法有效且令人满意:我使用 Excel 公式来解决这个问题。非整数数字乘以 1010,然后让 Excel 计算该数字除以 1010 的结果。这样,小数分隔符将始终根据每个系统的配置显示,并且仍然可以被任何配置读取。让我们看一个我们用于 Excel 中高度测量的公式的示例:=7749426479104/10000000000,这大约等于 774.9426 米。
历史
- 2005年3月12日:版本 1.06
- 首次发布。
- 2005年10月12日:版本 1.07
- 修复了
Plotter
中仅由一个点组成的轨迹片段会导致程序崩溃的问题。
- 修复了