前言
这个软件,前前后后断断续续做了两个星期,现在做成一个基本的版本,把当前功能记录下来,作为进一步开发的基础。我取名为NavsGo等等原因主要是Nav表示Navigator的简称,是导航器的意思。Go是出发的意思。NavsGo,有点像Let's Go的谐音,一起走的意思, 一方面表示这个软件作为GPS管理软件,可以协助多个导航软件一起运作,另一方面也希望该软件和使用者“一起走”。
背景
当前几乎所有Windows Mobile机型都自带GPS接收器,GPS渐渐成为我们生活中必不可少的一部分,可是在Windows Mobile直接下使用GPS导航不是一件容易的事情,需要配置硬件端口,软件端口,配置GPS Intermediate Driver, 配置AGPS,有时候甚至要配置GPRS上网(某些导航软件需要连接网络,例如Google Maps),更有些软件需要一个分离端口(splitted port)。因此做一个GPS管理软件,统一管理各项配置功能,调用现有的各种GPS导航软件实现导航。
关于
本文讲述一个GPS管理软件的开发,当前版本软件功能包括调用Google Maps,Tomtom,Garmin,iGO8,Route66等导航软件,调用GPS端口配置,AGP,split port等配置功能,基于GPS.net 3.0开发GPS诊断功能和GPS监控功能。界面上实现类iPhone菜单界面。在这篇文章中主要讲述GPS监控功能的实现。
功能
先展现一下系统的功能截图,有个整体的感性认识。
图1
这是系统的整体导航菜单,系统会自动检测已经安装的导航软件,如上图检测到Google Maps,Tomtom,Garmin,iGO8,Route66等导航软件。
图2
Google Maps,我心目中的NO.1,哪怕在室内也可以根据基站信息定位,是其他离线软件没有实现的。同时支持街景(Stree View)功能,交通状况功能,大大方便寻找,还有定位你自己的功能,把用户当前的位置信息通知朋友,甚至可以直接显示到facebook上 。反正功能多到超出想象,在无线网络日益完善的未来,Google Maps这类在线导航软件必定成为霸主,MS live map也属于这一类软件,大家拭目以待。缺点是需要上网费用,有些地图位置定位不够准确。
图3
Tomtom,平常一直在用,导航功能很不错,地图数据也比较准确,界面和易用性方面需要提高。
图4
iGO8,界面和易用性一流,可是导航地图信息有时候有点问题,导致我老是U-turn。
图5
Garmin,老牌GPS厂商,本人做的工业GPS产品一直使用Garmin的硬件,Garmin做产品有保障。关于Garmin的导航软件,我感觉易用性没有iGO8好,导航又比不上Tomtom。
图7
Port Splitter,一个免费软件,有些导航软件需要开了Port Splitter才能找到GPS receiver(GPS 硬件接收器)。以后版本打算把这个功能加入到NavsGo中,使用virtual com port的开发。
图8
端口管理和GPS Intermediate Driver管理,以后版本打算把把这个功能加入到NavsGo中。
图9
AGPS管理,以后版本打算把把这个功能加入到NavsGo中。
图11
GPS诊断功能,后面讲述如何实现。
以上各个导航软件的评价只是个人使用感觉,不代表各个导航软件的真实情况。
实现
GPS诊断功能和GPS监控功能是基于GPS.net 3.0实现的,关于GPS.net 3.0可以参考 GPS.NET 和 GeoFramework开源了。我觉得GPS.net是目前最好的基于.NET开发的GPS开源软件,所以我使用GPS.net作为NavsGo的基础。GPS.net 3.0比GPS.net 2.0做了很大改动,比如说把类结构进行大规模的调整,把NMEA分析功能实行简化,使用了泛型(Generic)封装异常处理等等。也导致了部分功能在2.0可用,到了3.0却不可以用,例如卫星控件。可能是我使用不当,也可能是源代码的问题。下面讲述如何使用GPS.net 3.0做一个GPS监控功能。
GPS设备检测功能
/* GPS.NET provides the ability to quickly discover GPS devices on the
* local system. This feature is known as "Automatic Device Discovery"
* and greatly simplifies the task of finding a GPS device to communicate
* with. Developers can simply call the "Start" method of an interpreter,
* and GPS.NET will automatically locate the best GPS Device to work with.
*
* During Device Discovery, several events are raised to indicate the
* progress of detection. This example hooks into almost all of the events
* to give users feedback.
*/
GeoFramework.Gps.IO.Devices.DeviceDetectionStarted += new EventHandler(Devices_DeviceDetectionStarted);
GeoFramework.Gps.IO.Devices.DeviceDetected += new EventHandler<GeoFramework.Gps.IO.DeviceEventArgs>(Devices_DeviceDetected);
GeoFramework.Gps.IO.Devices.DeviceDetectionCompleted += new EventHandler(Devices_DeviceDetectionCompleted);
GeoFramework.Gps.IO.Devices.DeviceDetectionAttempted += new EventHandler<GeoFramework.Gps.IO.DeviceEventArgs>(Devices_DeviceDetectionAttempted);
GeoFramework.Gps.IO.Devices.DeviceDetectionAttemptFailed += new EventHandler<GeoFramework.Gps.IO.DeviceDetectionExceptionEventArgs>(Devices_DeviceDetectionAttemptFailed);
GPS.net可以检测当前设备上的所有端口,这些端口包括GPS Intermediate Driver端口,普通串口,蓝牙串口等等,这个过程是使用多线程在后台运行的,为了处理检测结果,需要注册相关的事件,上面为注册过程,下面是处理函数。关于GPS Intermediate Driver可以参考 30 Days of .NET [Windows Mobile Applications] - Day 03: GPS Compass(GPS指南针)
#region GPS Device Detection Events
private void SearchButton_Click(object sender, EventArgs e)
{
// Start the GPS device detection process. This operation will complete
// in the background, on a separate thread. This method can be called again
// to attempt to locate new devices.
GeoFramework.Gps.IO.Devices.BeginDetection();
}
private void Devices_DeviceDetectionStarted(object sender, EventArgs e)
{
BeginInvoke(new MethodInvoker(delegate()
{
StatusBar1.Text = "Detecting GPS devices...";
SearchButton.Enabled = false;
}));
}
private void Devices_DeviceFailure(object sender, GeoFramework.Gps.IO.DeviceEventArgs e)
{
BeginInvoke(new MethodInvoker(delegate()
{
StatusBar1.Text = "Not Responding: " + e.Device.ToString();
}));
}
private void Devices_DeviceDetectionAttemptFailed(object sender, GeoFramework.Gps.IO.DeviceDetectionExceptionEventArgs e)
{
BeginInvoke(new MethodInvoker(delegate()
{
StatusBar1.Text = "Error: " + e.Exception.ToString();
}));
}
private void Devices_DeviceDetectionCompleted(object sender, EventArgs e)
{
BeginInvoke(new MethodInvoker(delegate()
{
StatusBar1.Text = "Device detection completed.";
SearchButton.Enabled = true;
StartButton.Enabled = true;
}));
}
private void Devices_DeviceDetectionAttempted(object sender, GeoFramework.Gps.IO.DeviceEventArgs e)
{
BeginInvoke(new MethodInvoker(delegate()
{
StatusBar1.Text = "Detecting " + e.Device.ToString();
}));
}
private void Devices_DeviceDetected(object sender, GeoFramework.Gps.IO.DeviceEventArgs e)
{
BeginInvoke(new MethodInvoker(delegate()
{
StatusBar1.Text = "Found Device: " + e.Device.ToString();
// Add it to the list box
ListViewItem DeviceItem = new ListViewItem(e.Device.ToString());
// Associate the detected device with this list view item, so we can connect
// to the device later on
DeviceItem.Tag = e.Device;
// Set the image of the new item
DeviceItem.ImageIndex = 0;
DevicesListView.Items.Add(DeviceItem);
}));
}
#endregion
当Devices_DeviceDetected被回调的时候表示有设备被检查出来,并把之加入列表框供用户选择。
由于使用多线程的关系,所有的界面更新需要使用Delegate,该deleagate定义如下:
/* The GPS.NET device detection process is multithreaded. As a result, we must
* use the Invoke and BeginInvoke methods to ensure that any updates to the form
* occur on the form's own thread. This delegate is used by such methods.
*/
public delegate void MethodInvoker();
监视GPS端口运行情况
检测当前设备上所有可用GPS端口后,可以绑定监视某个端口,使用NMEA解释器分析该端口的输出。下面是启动和停止监视某个端口的代码。
private void StartButton_Click(object sender, EventArgs e)
{
if (DevicesListView.SelectedIndices.Count == 0)
{
MessageBox.Show("Please Select the device to start.");
return;
}
// Find out which item is selected in the list view
ListViewItem SelectedItem = DevicesListView.Items[DevicesListView.SelectedIndices[0]];
// The "tag" property holds the device that was detected
Device selectedDevice = (Device)SelectedItem.Tag;
try
{
// Finally, start the interpreter using the newly-assigned Stream
Interpreter.Start(selectedDevice);
selectedDevice.Connecting += new EventHandler(Device_Connecting);
selectedDevice.Connected += new EventHandler(Device_Connected);
selectedDevice.Disconnecting += new EventHandler(Device_Disconnecting);
selectedDevice.Disconnected += new EventHandler(Device_Disconnected);
StartButton.Enabled = false;
// Enable the disconnect button
StopButton.Enabled = true;
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString(), "Unable to Connect", MessageBoxButtons.OK, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1);
}
}
private void StopButton_Click(object sender, EventArgs e)
{
// Stop any GPS communications
Interpreter.Stop();
StartButton.Enabled = true;
// And disable the disconnect button
StopButton.Enabled = false;
StatusBar1.Text = "Disconnected from GPS device.";
}
通过Interpreter.Start(selectedDevice);把选择的GPS设备端口绑定到NMEA解释器上,这样解释器就会分析该选定端口输出的数据了。同时注册该GPS设备建立链接和端口链接的事件。
当NMEA解释器对指定端口的数据进行分析的时候,可以订阅相关的事件对关心的数据进行处理,下面为NMEA解释器事件的订阅代码。
Interpreter.SpeedChanged += new System.EventHandler<GeoFramework.SpeedEventArgs>(Interpreter_SpeedChanged);
Interpreter.FixAcquired += new System.EventHandler(Interpreter_FixAcquired);
Interpreter.FixLost += new System.EventHandler(Interpreter_FixLost);
Interpreter.Resumed += new EventHandler(Interpreter_Resumed);
Interpreter.FixModeChanged += new System.EventHandler<GeoFramework.Gps.FixModeEventArgs>(Interpreter_FixModeChanged);
Interpreter.FixQualityChanged += new System.EventHandler<GeoFramework.Gps.FixQualityEventArgs>(Interpreter_FixQualityChanged);
Interpreter.HorizontalDilutionOfPrecisionChanged += new System.EventHandler<GeoFramework.Gps.DilutionOfPrecisionEventArgs>(Interpreter_HorizontalDilutionOfPrecisionChanged);
Interpreter.SentenceReceived += new System.EventHandler<GeoFramework.Gps.Nmea.NmeaSentenceEventArgs>(Interpreter_SentenceReceived);
Interpreter.AltitudeChanged += new System.EventHandler<GeoFramework.DistanceEventArgs>(Interpreter_AltitudeChanged);
Interpreter.PositionChanged += new System.EventHandler<GeoFramework.PositionEventArgs>(Interpreter_PositionChanged);
Interpreter.UtcDateTimeChanged += new System.EventHandler<GeoFramework.DateTimeEventArgs>(Interpreter_UtcDateTimeChanged);
Interpreter.Paused += new EventHandler(Interpreter_Paused);
Interpreter.BearingChanged += new System.EventHandler<GeoFramework.AzimuthEventArgs>(Interpreter_BearingChanged);
Interpreter.VerticalDilutionOfPrecisionChanged += new System.EventHandler<GeoFramework.Gps.DilutionOfPrecisionEventArgs>(Interpreter_VerticalDilutionOfPrecisionChanged);
Interpreter.SatellitesChanged += new EventHandler<SatelliteListEventArgs>(Interpreter_SatellitesChanged);
下面为部分事件处理代码.
void Interpreter_SentenceReceived(object sender, GeoFramework.Gps.Nmea.NmeaSentenceEventArgs e)
{
BeginInvoke(new MethodInvoker(delegate()
{
// Keep the list box from getting too big
if (RawDataListBox.Items.Count > 50)
RawDataListBox.Items.RemoveAt(0);
// Add the sentence to the raw data list box
RawDataListBox.Items.Add(e.Sentence.ToString());
RawDataListBox.SelectedIndex = RawDataListBox.Items.Count - 1;
}));
}
当NMEA解释器从指定端口中接收到Raw NMEA data时,会回调Interpreter_SentenceReceived,系统会把这些Raw NMEA data打印到列表框中。
时间变化
void Interpreter_UtcDateTimeChanged(object sender, GeoFramework.DateTimeEventArgs e)
{
BeginInvoke(new MethodInvoker(delegate()
{
// Update the current satellite-derived time
SatelliteTime.Text = e.DateTime.ToLocalTime().ToString();
}));
}
时间发生变化时进行回调,一般来说,设备会在每一秒或者每两秒钟输出NMEA data,重要的NMEA句子都会包含GPS时间的字段,关于NMEA可以参考.NET Compact Framework下的GPS NMEA data数据分析。这个输入时间的间隔和硬件有关,是可以设置的。事实上,GPS时间和UTC时间是有时间差的,目前(2009年)GPS时间比UTC时间快15秒,但是做应用开发一般不需要处理这个时间差,GPS硬件厂商的固件(Firmware)在输出NMEA data的时候已经进行调整,可以认为NMEA data的时间是UTC时间。
经度和纬度变化
void Interpreter_PositionChanged(object sender, GeoFramework.PositionEventArgs e)
{
BeginInvoke(new MethodInvoker(delegate()
{
// Update the latitude and longitude
Latitude.Text = e.Position.Latitude.ToString();
Longitude.Text = e.Position.Longitude.ToString();
StatusBar1.Text = "Position has changed.";
}));
}
经度和纬度信息发生变化时调用。
海拔变化
void Interpreter_AltitudeChanged(object sender, GeoFramework.DistanceEventArgs e)
{
BeginInvoke(new MethodInvoker(delegate()
{
altimeter1.Value = e.Distance;
// Update the current altitude
Altitude.Text = e.Distance.ToLocalUnitType().ToString();
StatusBar1.Text = "Altitude has changed.";
}));
}
海拔发生变化时调用,同时更新海拔计控件的信息。
方位变化
void Interpreter_BearingChanged(object sender, GeoFramework.AzimuthEventArgs e)
{
BeginInvoke(new MethodInvoker(delegate()
{
compass1.Value = e.Azimuth;
// Output the current bearing as degrees (i.e. 012°)
Bearing.Text = e.Azimuth.ToString();
// Output the current bearing as a compass direction (i.e. Southwest)
Direction.Text = e.Azimuth.ToString("c");
StatusBar1.Text = "Bearing has changed.";
}));
}
方位发生变化时调用,同时更新指南针控件的信息。
速度变化
void Interpreter_SpeedChanged(object sender, GeoFramework.SpeedEventArgs e)
{
BeginInvoke(new MethodInvoker(delegate()
{
speedometer1.Value = e.Speed;
// Update the current speed
Speed.Text = e.Speed.ToLocalUnitType().ToString();
StatusBar1.Text = "Speed has changed.";
}));
}
速度发生变化时调用,同时更新速度表控件的信息。
控件的使用
GPS.net 3.0提供了速度计,海拔计,指南针,卫星方位图和卫星信号图等控件。在上面的图可见,我使用了 速度计,海拔计,指南针等控件,可是我在使用卫星方位图和卫星信号图时发现些问题,我正在联系原作者帮助,可能是我使用的不对,也可能是GPS.net 3.0的bug,我会在下一个版本解决。
使用GPS.net 3.0的控件十分简单,只要把控件拖拉到winform里面,同时修改控件的IsPaintingOnSeparateThread属性为false。
compass1.IsPaintingOnSeparateThread = false;
altimeter1.IsPaintingOnSeparateThread = false;
speedometer1.IsPaintingOnSeparateThread = false;
//satelliteViewer1.IsPaintingOnSeparateThread = false;
//satelliteSignalBar1.IsPaintingOnSeparateThread = false;
在目前版本,如果不把IsPaintingOnSeparateThread修改为false会抛出一个多线程的异常信息。
at Microsoft.AGL.Common.MISC.HandleAr(PAL_ERROR ar)
at System.Windows.Forms.Control.get_Visible()
at GeoFramework.Drawing.DoubleBufferedControl.Repaint()
at GeoFramework.Drawing.DoubleBufferedControl.PaintingThreadProc()
这个问题我正在处理。
To-Do-List
当前只是第一个版本,有些功能需要继续不断完善,待完善功能如下:
1.实现端口管理功能。
2.实现AGPS功能。
3.实现检查飞行模式功能,因为飞行模式下GPS不能使用。
4.实现管理GPRS,3G网络链接功能。
5.在GPS监控模块增加对卫星的监控。
6.实现保存导航信息到Google maps及其他地图格式的功能。
7.优化UI处理。
......
关于项目的发展
我把项目host到codeplex了,打算不断改进。项目主页链接如下:
NavsGo - GPS management software下载当前版本的源代码链接如下
http://navsgo.codeplex.com/SourceControl/ListDownloadableCommits.aspx#DownloadLatest
检查和下载最新版本链接如下
http://navsgo.codeplex.com/SourceControl/ListDownloadableCommits.aspx
安装包 NavsGo.zip 由于博客园不能直接上传cab文件,请解压成 NavsGo.cab,然后拷贝到Windows Mobile设备上安装,谢谢。
下篇文章会讲一下GPS诊断功能的开发,然后讲类iPhone界面的开发。