简述
在上篇文章 Windows Mobile下GPS管理软件NavsGo之GPS监控功能的开发 概述了NavsGo项目以及讲述了GPS监控功能的开发,GPS.net控件的使用,这篇文章讲述侦测功能的开发。
关于
所谓GPS侦测功能就是扫描手机上所有可用的GPS设备(available GPS devices),把各个设备运行状态展现给用户,如果发现问题,通过友善的方式提示用户如果解决设备连通性问题。这些建议包括启动GPS设备,修改GPS Intermediate Driver的配置,启动蓝牙GPS设备等等。
实现
这个模块是GPS.net 作者jperson的一个demo程序,原程序可以到 GPS Diagnostics for .NET 下载。我做了少量修改加到NavsGo里面来了。
发现/检测功能(Detection)
发现功能在上篇文章讲过,通过注册静态事件,然后回调相应的处理函数。
/* Hook into GPS device detection events. These events are static, allowing them to
* be easily sunk by any other class or form.
*/
Devices.DeviceDetectionStarted += new EventHandler(Devices_DeviceDetectionStarted);
Devices.DeviceDetectionCompleted += new EventHandler(Devices_DeviceDetectionCompleted);
Devices.DeviceDetected += new EventHandler<DeviceEventArgs>(Devices_DeviceDetected);
Devices.DeviceDetectionAttempted += new EventHandler<DeviceEventArgs>(Devices_DeviceDetectionAttempted);
Devices.DeviceDetectionAttemptFailed += new EventHandler<DeviceDetectionExceptionEventArgs>(Devices_DeviceDetectionAttemptFailed);
由于GPS.net是开源的,我们这次钻到他的源代码看看Detection的实现逻辑。
启动发现功能是在 Devices.BeginDetection() 函数里面。
public static void BeginDetection()
{
// Start detection on another thread.
if (_IsDetectionInProgress)
return;
// Signal that detection is in progress
_IsDetectionInProgress = true;
// Start a thread for managing detection
_DetectionThread = new Thread(new ThreadStart(DetectionThreadProc));
_DetectionThread.Name = "GPS.NET Device Detector (http://www.geoframeworks.com)";
_DetectionThread.IsBackground = true;
#if !PocketPC
// Do detection in the background
_DetectionThread.Priority = ThreadPriority.Lowest;
#endif
_DetectionThread.Start();
#if PocketPC
// Signal that the thread is alive (no Thread.IsAlive on the CF :P)
_IsDetectionThreadAlive = true;
#endif
}
启动发现过程,系统会启动一个线程调用DetectionThreadProc()进行发现。下面是DetectionThreadProc()函数。
private static void DetectionThreadProc()
{
try
{
// Signal that it started
OnDeviceDetectionStarted();
// Monitor this thread up to the timeout, then quit
ThreadPool.QueueUserWorkItem(new WaitCallback(DetectionThreadProcWatcher));
#if PocketPC
// Are we using the GPS Intermediate Driver?
GpsIntermediateDriver gpsid = GpsIntermediateDriver.Current;
// Is the GPSID supported?
if (gpsid != null)
{
// Yes. Test it to be sure
gpsid.BeginDetection();
// Wait for one device to get detected. Was it confirmed?
if (gpsid.WaitForDetection())
{
// Yes. If we only need one device, exit
if(_IsOnlyFirstDeviceDetected)
return;
}
}
#endif
/* If we get here, the GPS Intermediate Driver is not responding! */
int count;
#region Detect Bluetooth devices
// Is Bluetooth supported and turned on?
if (IsBluetoothSupported && IsBluetoothEnabled)
{
// Start bluetooth detection for each device
count = _BluetoothDevices.Count;
for (int index = 0; index < count; index++)
_BluetoothDevices[index].BeginDetection();
}
#endregion
#region Detect serial GPS devices
if (AllowSerialConnections)
{
count = SerialDevices.Count;
for (int index = 0; index < count; index++)
_SerialDevices[index].BeginDetection();
/* If we're performing "exhaustive" detection, ports are scanned
* even if there's no evidence they actually exist. This can happen in rare
* cases, such as when a PCMCIA GPS device is plugged in and fails to create
* a registry entry.
*/
if (_AllowExhaustiveSerialPortScanning)
{
// Try all ports from COM0: up to the maximum port number
for (int index = 0; index < _MaximumSerialPortNumber; index++)
{
// Is this port already being checked?
bool alreadyBeingScanned = false;
for (int existingIndex = 0; existingIndex < _SerialDevices.Count; existingIndex++)
{
if (_SerialDevices[existingIndex].PortNumber.Equals(index))
{
// Yes. Don't test it again
alreadyBeingScanned = true;
break;
}
// If it's already being scanned, stop
if (alreadyBeingScanned)
break;
}
// If it's already being scanned, skip to the next port
if (alreadyBeingScanned)
continue;
// This is a new device. Scan it
SerialDevice exhaustivePort = new SerialDevice("COM" + index.ToString() + ":");
exhaustivePort.BeginDetection();
}
}
}
#endregion
#region Discover new Bluetooth devices
// Is Bluetooth supported and turned on?
if (IsBluetoothSupported && IsBluetoothEnabled)
{
/* NOTE: For mobile devices, only one connection is allowed at a time.
* As a result, we use a static SyncRoot to ensure that connections
* and discovery happens in serial. For this reason, we will not attempt
* to discover devices until *after* trying to detect existing ones.
*/
#if PocketPC
// Wait for existing devices to be tested
count = _BluetoothDevices.Count;
for (int index = 0; index < count; index++)
{
// Complete detection for this device
_BluetoothDevices[index].WaitForDetection();
}
#endif
// Begin searching for brand new devices
BluetoothDevice.DiscoverDevices(true);
// Block until that search completes
BluetoothDevice.DeviceDiscoveryThread.Join();
}
#endregion
#region Wait for all devices to finish detection
/* A list holds the wait handles of devices being detected. When it is empty,
* detection has finished on all threads.
*/
while (_CurrentlyDetectingWaitHandles.Count != 0)
{
try
{
ManualResetEvent handle = _CurrentlyDetectingWaitHandles[0];
#if !PocketPC
if (!handle.SafeWaitHandle.IsClosed)
#endif
handle.WaitOne();
}
catch (ObjectDisposedException)
{
/* In some rare cases a device will get disposed of and nulled out.
* So, regardless of what happens we can remove the item.
*/
}
finally
{
_CurrentlyDetectingWaitHandles.RemoveAt(0);
}
}
#endregion
#if PocketPC
#region Reconfigure the GPS Intermediate Driver (if necessary)
/* The GPS Intermediate Driver may not have the right "Program Port" (actual GPS port/baud rate)
* settings. Now that detection has completed, let's see if the GPSID needs configuration.
* If it is flagged as NOT being a GPS device, then it could not connect. In this case, let's
* find the most reliable serial device and use it.
*/
if (
// Is the GPSID supported?
gpsid != null
// Are we allowed to configure it?
&& gpsid.IsAutomaticallyConfigured
// Is it currently NOT identified as a GPS device? (connections failed)
&& !gpsid.IsGpsDevice)
{
// Look through each confirmed GPS device
count = _GpsDevices.Count;
for (int index = 0; index < count; index++)
{
// Is it a serial device?
SerialDevice device = _GpsDevices[index] as SerialDevice;
if (device == null)
continue;
// Yes. Use it!
try
{
gpsid.HardwarePort = device;
// The GPSID is now working
Add(gpsid);
}
catch (Exception ex)
{
// Notify of the error gracefully
OnDeviceDetectionAttemptFailed(new DeviceDetectionException(gpsid, ex));
}
// That's the best device, so quit
break;
}
}
#endregion
#endif
// Signal completion
OnDeviceDetectionCompleted();
}
catch (ThreadAbortException)
{
#region Abort detection for all devices
#if PocketPC
// Stop detection for the GPSID
if(GpsIntermediateDriver.Current != null)
GpsIntermediateDriver.Current.CancelDetection();
#endif
// Stop detection for each Bluetooth device
for (int index = 0; index < _BluetoothDevices.Count; index++)
_BluetoothDevices[index].CancelDetection();
// Stop detection for each serial device
for (int index = 0; index < _SerialDevices.Count; index++)
_SerialDevices[index].CancelDetection();
#endregion
// Wait for all the threads to die. Just... sit and watch. And wait.
while (_CurrentlyDetectingWaitHandles.Count != 0)
{
try { _CurrentlyDetectingWaitHandles[0].WaitOne(); }
catch { }
finally { _CurrentlyDetectingWaitHandles.RemoveAt(0); }
}
// Signal the cancellation
if (DeviceDetectionCanceled != null)
DeviceDetectionCanceled(null, EventArgs.Empty);
}
finally
{
// Detection is no longer in progress
_DetectionCompleteWaitHandle.Set();
_CurrentlyDetectingWaitHandles.Clear(); // <-- Already empty?
_IsDetectionInProgress = false;
#if PocketPC
// Signal that the thread is alive (no Thread.IsAlive on the CF :P)
_IsDetectionThreadAlive = false;
#endif
}
}
DetectionThreadProc()负责整个发现过程,是一个很长的函数,有必要重构一下,把它分离(split)成几个小函数。他的处理逻辑是,检测超时,一旦发现发现过程超时,就好中途停止所有的处理。然后按顺序检测设备。检测的设备包括GPS Intermediate Driver设备(GpsIntermediateDriver),串口设备(SerialDevice)和蓝牙设备(BluetoothDevice)。支持的设备类图如下:
所有设备都是继承于父类Device,这样可以通过容器类Devices类统一管理所有设备的对象,通过多态的方式去调用各个具体设备的处理函数来实现发现过程。对于每个独立的设备,他们统一发现流程是启动一个线程,然后试图打开该设备,如果超时,认为设备不可用,如果在超时之前读取到数据就分析输出数据,如果数据是标准的NMEA就认为这个是GPS设备。NMEA相关的可以参考.NET Compact Framework下的GPS NMEA data数据分析。
GPS Intermediate Driver设备的发现
发现过程首先检查的是GPS Intermediate Driver设备。在Windows Mobile 5+和Wince 6+的系统下一般都内嵌GPS Intermediate Driver,关于GPS Intermediate Driver的开发可以参考30 Days of .NET [Windows Mobile Applications] - Day 03: GPS Compass(GPS指南针) 。我计划增加 GPS Intermediate Driver管理功能到NavsGo里面,后续会把GPS Intermediate Driver管理的开发写下来。
已经配对了的蓝牙设备的发现
检测完GPS Intermediate Driver设备,就开始检查已经配对了的蓝牙设备(Paired Bluetooth Devices),这些已经配对了的蓝牙设备保存在注册表HKEY_LOCAL_MACHINE\SOFTWARE\GeoFrameworks\GPS.NET\3.0\Devices\Bluetooth\中。关于蓝牙配对也可以参考一下 .NET Compact Framework下的Bluetooth设备的配对。
软件意义上的串口设备的发现
检测完已经配对了的蓝牙设备后,就开始检查串口设备,这里的串口设备是指软件意义上的串口,不是仅仅只通过硬件串口线连接的设备。由于 NMEA 0183 的规范规定GPS设备的联通性通过波特率(Baud rate)为4800的串口设备。所以GPS设备厂商尽管使用其他联通方式,但是都可以转成软件上的串口设备。例如USB GPS设备可以通过驱动转成串口设备,蓝牙可以建立虚拟串口,关于虚拟串口可以参考 .NET Compact Framework下的Bluetooth开发 之 Bluetooth Virtual Serial Port。总的来说,这里软件意义上的串口,真实的设备可能是 真正的串口线,USB,PCMCIA,蓝牙,红外等。
新蓝牙设备的发现
做完GPS Intermediate Driver设备,已经配对了的蓝牙设备和软件意义上的串口的发现流程后,进入了对新蓝牙设备的发现过程,这些蓝牙设备是不在已经配对了的蓝牙设备的范畴里面的,是手机周边新的蓝牙设备。由于手机对蓝牙的通信只能是一对一,也就是一个时间内一台手机只能和一个蓝牙设备进行通信,所以在发现新蓝牙设备之前,需要先等待已经配对了的蓝牙设备的发现过程的结束。关于蓝牙设备的开发和发现可以参考一下 .NET Compact Framework下的Bluetooth开发 之 Windows Embedded Source Tools for Bluetooth 和 .NET Compact Framework下的Bluetooth开发 之 32feet.NET。
GPS Intermediate Driver设备配置
一般来说移动设备都是通过GPS Intermediate Driver设备来对外部GPS程序提供服务的,有时候尽管GPS Intermediate Driver设备存在,并在运行,但是由于配置不正确也会导致外部GPS程序未能正确连接和使用GPS设备。所以最后一步是检查GPS Intermediate Driver设备的配置情况。我计划也在NavsGo增加GPS Intermediate Driver设备的配置管理功能。
最后的最后是清理所有资源,一个好的程序的习惯。
建议功能
发现功能实现了整个GPS诊断模块的核心,建议功能也就是呈现发现功能缓存的信息。建议功能实现在SummaryForm和DeviceForm两个类里面,SummaryForm提示建议,而DeviceForm现实某个设备的检查情况。
建议功能的流程是,判断GPS Intermediate Driver设备是否可用,如果不可用可能是硬件端口配置错误,建议把可用的串口端口配置为GPS Intermediate Driver的硬件端口,如果没有可用的串口端口,建议使用蓝牙设备。
日志功能
GPS.net提供日志发送功能,把设备发现和检查信息发送到服务器,方便开发者改进。这个功能很简单。
源码请看 Windows Mobile下GPS管理软件NavsGo之GPS监控功能的开发
本文转自Jake Lin博客园博客,原文链接:http://www.cnblogs.com/procoder/archive/2009/08/21/1551155.html,如需转载请自行联系原作者