📄 gpsreader.cs
字号:
// GPSReader.cs
//
// Copyright (C) 2003 JW Hedgehog, Inc. All rights reserved
//
// JW Hedgehog, Inc
// http://www.jwhh.com
//
// Direct questions to mailto:jimw@jwhh.com
//
// This code, comments and information are provided "AS IS" with
// no warrenty of any kind, either expressed or implied, including
// but not limited to the implied warranties of merchentability and/or
// fitness for a particular purpose
// ---------------------------------------------------------------------
using System;
using System.Data;
using System.Windows.Forms ;
using System.Runtime.InteropServices ;
using System.Text ;
using System.Collections ;
using System.Threading ;
using System.IO ;
namespace GPSExample.Util
{
/// <summary>
/// Class that manages the GPS reading process.
///
/// To get started using the class do the following
/// 1) Construct GPSReader passing the port name and baud rate of the GPS device
/// C#: GPSReader gps = new GPSReader("COM4:", 4800) ;
/// VB: Dim WithEvents gps As New GPSReader("COM4:", 4800)
/// 2) Handle the OnGPSMessage event
/// This event will fire each time the GPS sends an update
/// 3) Call gps.StartRead()
/// Launches the GPS reading process on a background thread
///
/// Use the StartRead and StopRead methods to control the GPS reading process. Before calling
/// StartRead you must provide at least the port name in the form "COMx" (x is the port number) and
/// the baud rate. You can do this using either a constructor or the PortName and BaudRate properties.
/// Each time a GPS message is received, the OnGPSMessage event will fire passing an instance of the
/// GPSEventArgs class containing the raw GPS sentence along with some of the values already parsed into
/// read-only fields.
///
/// This class does the actual GPS reading work on a background thread. The individual OnGPSMessage events
/// are raised in a UI thread safe manner so no special handling is required. Because it is considered unsafe
/// to interact with UI elements (TextBox, ListBox, etc.) from a thread other then the thread on which they were
/// created, the GPS reader thread raises the OnGPSMessage event on the UI thread. These is acheived by deriving
/// the GPSReader class from Control and then using the inherited Invoke method. Calling this.Invoke causes the
/// event to be raised on the same thread on which the GPSReader was created. Since the GPSReader is usually
/// created as a member of either a Form or a method on a Form, it is safe to assume that the GPSReader was
/// created on the same thread as the Form and the Form's associated UI elements.
///
/// Because there can sometimes be a short delay between when StartRead/StopRead are called and when the
/// action actually occurs on the background thread, OnGPSStartRead and OnGPSStopRead events are provided.
/// Each fires when the background thread actually performs the action. Like the OnGPSMessage event,
/// these are raised in a UI thread safe manner.
///
/// To support the broadest number of GPS devices, the class actually supports two different read modes.
/// The preferred read mode is "MesssageMode". In MessageMode, we let the COMM port driver monitor the
/// GPS stream watching for the arrival of the carriage-return (\n). Our code blocks until the COMM port
/// driver notifies us of the carriage-return, at which time we then go read the entire GPS sentence from
/// the COMM port driver.
/// The alternative read mode is "CharacterMode". In CharacterMode we read the data character-by-character
/// from the COMM port driver manually building the GPS sentence and watching for the carriage-return. This
/// mode was added because experimentation showed that some GPS devices that simulate COMM ports(i.e. the GPS
/// might be an expansion pack of compact flash card but appears as a COMM port to the device) do not
/// support letting the COMM port driver monitor for the carriage-return.
/// Using the PreferredReadMode property, you can set which mode the GPSReader uses If you choose MessageMode
/// or Auto (the default) The GPSReader class tests to see if the driver supports MessageMode and if so use it.
/// Otherwise it will downgrade to CharacterMode. The ActiveReadMode property indicates which read mode is
/// actually being used.
/// The code that tests for MessageMode support is in the "DriverSupportsMessageMode method. Because its not
/// possible to test every GPS in existence there is no way to be 100% sure that this test will always work but
/// on the devices tested it has been reliable.
/// ***************************************************************************************************************
/// Note ***********************************************************
/// If you try reading from a device and the GPSReader never returns any data, the cause may be that MessageMode
/// support has been falsly indicated as supported. Setting the GPSReader PreferredReadMode to ReadMode.Character
/// should over come the problem. The need to do this has never been observered but since its not possible to test
/// every GPS device the possibility always exists.
/// ***************************************************************************************************************
/// </summary>
public class GPSReader : Control
{
// *************************************************************
// Constructors
// *************************************************************
/// <summary>
/// Default constructor
/// At a minimum, will need to set the PortName and BaudRate properties before calling StartRead
/// </summary>
public GPSReader()
{
}
/// <summary>
/// Constructor - Accepts COMM port name (COMx:)
/// Will need to set the BaudRate properties before calling StartRead
/// </summary>
/// <param name="portName"></param>
public GPSReader(string portName)
: this()
{
_portName = portName ;
}
/// <summary>
/// Constructor - Accepts COMM port name (COMx:) and BaudRate
/// If default COMM port settings (NoParity, 8 bits/byte and OneStopBit) are acceptable,
/// can call StartRead without setting any of the configuration properties
/// </summary>
/// <param name="portName"></param>
/// <param name="baudRate"></param>
public GPSReader(string portName, int baudRate)
: this(portName)
{
_baudRate = baudRate ;
}
/// <summary>
/// Constructor - verbose
/// Provides full control over all COMM port settings
/// </summary>
/// <param name="portName"></param>
/// <param name="baudRate"></param>
/// <param name="parity"></param>
/// <param name="byteSize"></param>
/// <param name="stopBits"></param>
public GPSReader(string portName, int baudRate, ParitySetting parity, byte byteSize, StopBitsSetting stopBits)
: this(portName, baudRate)
{
_parity = parity ;
_byteSize = byteSize ;
_stopBits = stopBits ;
}
// *************************************************************
// Events
// *************************************************************
/// <summary>
/// Fires each time a GPS message is received
/// </summary>
public event GPSEventHandler OnGPSMessage ;
/// <summary>
/// Fires when the background thread begins the read process
/// </summary>
public event EventHandler OnGPSReadStart ;
/// <summary>
/// Fires when the background thread exits the read process
/// </summary>
public event EventHandler OnGPSReadStop ;
// *************************************************************
// Start/Stop Reading
// *************************************************************
/// <summary>
/// Initiate GPS Reading
/// Actual reading done on a background thread - this method returns immediatly
///
/// Throws an error if either PortName or BaudRate not set
/// </summary>
public void StartRead()
{
if (_readData == true)
return;
// Verify that we know the port name and baud rate
if (_baudRate == baudRateNotSet || _portName == portNameNotSet)
throw new ApplicationException("<GPSReader> Must set Baud Rate & Port Name before opening the port") ;
Cursor.Current = Cursors.WaitCursor ;
_readData = true ;
_gpsReadThread = new Thread(new ThreadStart(this.GPSReadLoop)) ;
_gpsReadThread.Start() ;
Cursor.Current = Cursors.Default ;
}
/// <summary>
/// Terminate GPS Reading
/// Sets _readData to false which exits the underlying read loop
/// Also closes the COMM port which aborts any pending COMM port operations
/// </summary>
public void StopRead()
{
//if (_readData == false)
// return;
Cursor.Current = Cursors.WaitCursor ;
_readData = false ;
Thread.Sleep(500) ; // Give thread time to finish any pending work
ClosePort() ;
Cursor.Current = Cursors.Default ;
}
// *************************************************************
// Port Setup and configuration
// *************************************************************
/// <summary>
/// Set Port Name (COMx:)
/// </summary>
public string PortName
{
get { return _portName ;}
set {_portName = value ;}
}
/// <summary>
/// Set Baud Rate - No Default
/// </summary>
public int BaudRate
{
get { return _baudRate ;}
set {_baudRate = value ;}
}
/// <summary>
/// Set Port Parity - defaults to NoParity
/// </summary>
public ParitySetting Parity
{
get { return _parity ;}
set {_parity = value ;}
}
/// <summary>
/// Set Port StopBits - defaults to OneStopBit
/// </summary>
public StopBitsSetting StopBits
{
get { return _stopBits ;}
set {_stopBits = value ;}
}
/// <summary>
/// Set Port Byte Size (in bits) - defaults to 8
/// </summary>
public byte ByteSize
{
get { return _byteSize ;}
set {_byteSize = value ;}
}
// *************************************************************
// Port Reading
// *************************************************************
/// <summary>
/// Main Read Loop
/// After openning the COMM Port, repeatedly retrieves a GPS sentence.
/// If the sentence appears correct (starts with $GP) the GPSMessage event is fired
/// If an exception should occur, the COMM Port is closed and the exception is propagated
/// </summary>
private void GPSReadLoop()
{
EventHandler GPSMessageHandler = new EventHandler(this.DispatchGPSMessage);
EventHandler GPSReadStartHandler = new EventHandler(this.DispatchGPSReadStart);
EventHandler GPSReadStopHandler = new EventHandler(this.DispatchGPSReadStop);
try //MOD
{
OpenPort() ;
// Signal beginning of read process - event fired on UI thread
this.Invoke(GPSReadStartHandler) ;
//try //MOD
//{
gpsSentence = ReadSentence() ;
while (gpsSentence != null) // will only be null if StopRead() is called
{
// If appears to be valid message, Signal GPS Message received - event fired on UI thread
// Invoke blocks this thread until the method wrapped by the GPSMessageHandler returns.
// If this code is ever changed to an asynchronous method of execution then the gpsSentence
// variable will have to be protected against simultaneous access.
if (gpsSentence.StartsWith("$GP"))
this.Invoke(GPSMessageHandler) ;
gpsSentence = ReadSentence() ;
}
}
catch (Exception e)
{ // If any exception is thrown, close the COMM port and propagate the exception
ClosePort() ;
//throw e ; //MOD
}
// Signal end of read process - event fired on UI thread
this.Invoke(GPSReadStopHandler) ;
}
/// <summary>
/// Handoff to the appopriate ReadSentence based on active ReadMode
/// </summary>
/// <returns>GPS Sentence</returns>
private string ReadSentence()
{
string returnVal = string.Empty ;
if (_activeReadMode == ReadMode.Message)
returnVal = ReadSentence_Message() ;
else
returnVal = ReadSentence_Character() ;
return returnVal ;
}
/// <summary>
/// Retrieves the sentence and translates from ASCII to Unicode
/// </summary>
/// <returns>GPS Sentence</returns>
private string ReadSentence_Message()
{
Byte[] buffer;
Byte[] temp = new Byte[1] ;
int numBytesRead = 0 ;
Encoding asciiConverter = Encoding.ASCII ;
// keep reading until we are told to stop or we get something
// looped read should only occur if read errors are encountered
bool portReturnedData = ReadPort_Message(MAX_MESSAGE, out buffer, out numBytesRead) ;
while (_readData && ! portReturnedData)
portReturnedData = ReadPort_Message(MAX_MESSAGE, out buffer, out numBytesRead) ;
// if still reading, Translate from ASCII to Unicode
return _readData ? asciiConverter.GetString(buffer, 0, numBytesRead) : null ;
}
/// <summary>
/// Builds the sentence by doing single character reads then translates from ASCII to Unicode
/// Additional code is used to adjust whether translation starts at the beginning of the string
/// or skips the first character. This had to be added because one of the GPS simulators we were
/// using would send an extra character after the carriage-return (\n).
/// </summary>
/// <returns>GPS Sentence</returns>
public string ReadSentence_Character()
{
Byte[] data = new Byte[MAX_MESSAGE] ;
Byte temp = 0 ;
int pos = 0 ;
Encoding asciiConverter = Encoding.ASCII ;
// Build the sentence until carriage-return encountered
while(_readData && temp != endOfGPSSentenceMarker)
{
temp = ReadPort_Character() ;
data[pos++] = temp ;
}
// Translation adjustment to handle extra character sent by some simulation programs
int translateStartPos = 0 ;
int translateCount = pos ;
if (data[0] != (Byte) '$')
{
translateStartPos++ ;
translateCount-- ;
}
// Perform translation
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -