📄 gpsreader.cs
字号:
return _readData ? asciiConverter.GetString(data, translateStartPos, translateCount) : null;
}
// *************************************************************
// Read mode related methods and properties
// *************************************************************
/// <summary>
/// Indicates whether the COMM Port driver supports reading entire GPS messages at once
/// The preferred way to read data is to let the driver signal when a carriage-return (\n)
/// is received and then we read the whole message at once. Experimentation has shown that
/// some of the GPS devices that simulate a serial port do not support this mode. In that
/// case, we need to read the port data character-by-character.
/// On the GPS devices tested, support for character notification can be verified by attempting
/// to set the EvtChar (event character) member of the DCB structure using SetCommState and then
/// reading the DCB back with GetCommState. If character notification is supported the returned
/// DCB will contain the EvtChar that was set with SetCommState. If it is not supported, EvtChar
/// will contain 0. 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.
/// </summary>
/// <returns></returns>
public bool DriverSupportsMessageMode()
{
// Verify that we know the port name
if (_portName == portNameNotSet)
throw new ApplicationException("<DriverSupportsMessageMode> Must set Port Name before calling this method") ;
uint localPortHandle = INVALID_FILE_HANDLE ;
DCB dcb = new DCB() ;
// Check to see if port is currently open
bool openPortLocally = _portHandle == INVALID_FILE_HANDLE ;
// If port not open then open it, otherwise use current handle
localPortHandle = openPortLocally ? OpenPort_Raw(_portName) : _portHandle ;
if (localPortHandle == INVALID_FILE_HANDLE)
throw new ApplicationException("<DriverSupportsMessageMode> Invalid port: " + _portName) ;
// Get current port settings
GetCommState(localPortHandle, dcb) ;
// Attempt to set event character
dcb.EvtChar = endOfGPSSentenceMarker ;
SetCommState(localPortHandle, dcb) ;
// Read port settings back
GetCommState(localPortHandle, dcb) ;
// if port opened locally - close it
if (openPortLocally)
CloseHandle(localPortHandle) ;
// Check to see if driver accepted event character
return dcb.EvtChar == endOfGPSSentenceMarker ;
}
/// <summary>
/// Preferred Read Mode to use - Defaults to Auto
/// In Auto, reader will attempt message mode if driver supports it, otherwise uses character-by-character mode
/// Undefined doesn't make sense in this usage, so if someone tries to set the value to Undefined, make it Auto
/// </summary>
public ReadMode PreferredReadMode
{
get {return _preferredReadMode;}
set {_preferredReadMode = (value == ReadMode.Undefined) ? ReadMode.Auto : value ; }
}
/// <summary>
/// Read Mode that the GPSReader is actually using
/// Set to Undefined until reading is started
/// </summary>
public ReadMode ActiveReadMode
{
get {return _activeReadMode;}
}
// *************************************************************
// Other COMM port related methods
// *************************************************************
/// <summary>
/// This method doesn't actually have anything to do with GPS reading but is helpful for determining
/// which ports are available on the device
/// The code simply attempts to open (and immediatly close) all COMM ports between COM1: and COM9:, if it opens
/// successfully, then it is added to the port list
/// </summary>
/// <returns>string array of available ports</returns>
public static string[] GetPortList()
{
ArrayList portList = new ArrayList() ;
uint hPort = INVALID_FILE_HANDLE ;
// Walk list of possible ports
for (int i = 1; i < 10; i++)
{
string port = "COM" + i.ToString() + ":" ;
hPort = OpenPort_Raw(port) ;
if (hPort != INVALID_FILE_HANDLE)
{
portList.Add(port) ;
CloseHandle(hPort) ;
}
}
// Convert to regular string array
return (string []) portList.ToArray(typeof(string)) ;
}
#region Constants
private const uint GENERIC_READ = 0x80000000;
private const uint OPEN_EXISTING = 3;
private const uint INVALID_FILE_HANDLE = 0xFFFFFFFF ;
private const uint EV_RXFLAG = 0x0002 ;
private const int baudRateNotSet = 0 ;
private const string portNameNotSet = "PortNotSet" ;
private const ParitySetting defaultParity = ParitySetting.NoParity;
private const StopBitsSetting defaultStopBits = StopBitsSetting.OneStopBit ;
private const byte defaultByteSize = 8 ;
private const sbyte endOfGPSSentenceMarker = (sbyte)'\n' ;
private const int MAX_MESSAGE = 256 ;
#endregion
#region Private fields
private string _portName = portNameNotSet ; // COMM Port Name - must end with ":"
private int _baudRate = baudRateNotSet ; // COMM Port Baud Rate
private ParitySetting _parity = defaultParity ; // COMM Port Parity - Defaults to NoParity
private StopBitsSetting _stopBits = defaultStopBits ; // COMM Port Stop Bits - Defaults to OneStopBit
private byte _byteSize = defaultByteSize ; // COM Port Byte Size - Defaults to 8 bits/byte
private uint _portHandle = INVALID_FILE_HANDLE ; // COMM Port File Handle
private Thread _gpsReadThread ; // Reader Thread
private bool _readData = false ; // Indicates whether read loop should continue
public bool ReadData
{
get
{
return _readData;
}
}
// Read mode indicates how messages should be read. The most efficient is to read whole messages
// from the COMM port but not all GPS drivers support this mode. The alternative is to manually
// build the messages by reading a character at a time from the driver.
private ReadMode _preferredReadMode = ReadMode.Auto ; // COMM Port Preferred Read Mode - Auto lets the reader decide
private ReadMode _activeReadMode = ReadMode.Undefined ; // COMM Port Read Mode being used - Undefined until reading starts
// gpsSentence is populated by the GPS read thread and consumed by the UI Thread. No lock
// is currently required because the GPS read thread uses Control.Invoke, which is synchronous, to signal the UI thread.
// If the code is ever changed to use an asynchronous notification then gpsSentence will need to be protected from simulaneous
// access.
private string gpsSentence ; // Represents the current GPS Sentence
#endregion
#region Cleanup
/// <summary>
/// Terminates GPS reading as part of dispose process
/// </summary>
/// <param name="disposing"></param>
protected override void Dispose(bool disposing)
{
if (disposing)
ClosePort() ;
GC.SuppressFinalize(this) ;
}
/// <summary>
/// Catch all cleanup - if StopRead was never called and Dispose wasn't called then
/// force shutdown as part of Garbage Collection - hopefully this method is never used
/// </summary>
~GPSReader()
{
ClosePort() ;
}
#endregion
#region Event Dispatch Helper Methods
/// <summary>
/// Raise Events back to listeners
/// These are helper methods to raise events to the UI layer. Because updating UI elements from
/// background threads is considered unsafe, these methods are initiated from the background reader
/// thread using this.Invoke which causes these methods to run on the same thread that the GPSReader
/// class was originally created on (usually the same thread as the application UI). These methods
/// now do a regular event fire to notify the UI of the actual event.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void DispatchGPSMessage(object sender, EventArgs e)
{
try
{
//this has exceptioned before, on GPSEventArgs constructor
GPSEventArgs arg = new GPSEventArgs(this.gpsSentence);
if (arg.Lat != lastLat && arg.Lon != lastLon)
{
lastLat = arg.Lat;
lastLon = arg.Lon;
if (OnGPSMessage != null)
OnGPSMessage(this, arg);
}
//else dont send
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.ToString());
}
}
private double lastLat = Double.MinValue;
private double lastLon = Double.MinValue;
private void DispatchGPSReadStart(object sender, EventArgs e)
{
if (OnGPSReadStart != null)
OnGPSReadStart(this, EventArgs.Empty) ;
}
private void DispatchGPSReadStop(object sender, EventArgs e)
{
if (OnGPSReadStop != null)
OnGPSReadStop(this, EventArgs.Empty) ;
}
#endregion
#region COMM Port Housekeeping Methods
/// <summary>
/// Open COMM Port
/// Configures the Port communication values and timeouts
/// </summary>
private void OpenPort()
{
if (_portHandle == INVALID_FILE_HANDLE) // Only open if not yet opened
{
_portHandle = OpenPort_Raw(_portName) ;
if (_portHandle == INVALID_FILE_HANDLE)
throw new ApplicationException("<OpenPort> Unable to Open Port: " + _portName) ;
System.Threading.Thread.Sleep(100) ; // Some experiments showed problems when the state
// was set without a short pause after Open
_activeReadMode = DetermineActiveReadMode() ; // Determine Read Mode to use
ConfigurePort(_baudRate, _parity, _byteSize, _stopBits) ;
SetReadTimeOuts() ;
}
}
/// <summary>
/// Determine the active read mode to use based on the preferred read mode and driver capability
/// Defaults to character mode because works with all drivers
/// Will attempt message mode if Message or Auto preferred - will downgrade to Character if not supported
/// </summary>
/// <returns>ReadMode to use</returns>
private ReadMode DetermineActiveReadMode()
{
ReadMode returnVal = ReadMode.Character ; // Default to character - everyone supports it
// If Message mode or Auto preferred - Set active to Message Mode if the driver supports it
if (_preferredReadMode == ReadMode.Message || _preferredReadMode == ReadMode.Auto)
{
if (DriverSupportsMessageMode())
returnVal = ReadMode.Message ;
}
return returnVal ;
}
/// <summary>
/// Close COMM port
/// </summary>
private void ClosePort()
{
if (_portHandle != INVALID_FILE_HANDLE)
{
CloseHandle(_portHandle) ;
_portHandle = INVALID_FILE_HANDLE ;
}
}
/// <summary>
/// Set COMM port configuration values
/// Baud Rate, Parity (default: NoParity), Bits per Byte (default: 8) and Stop Bits (default: OneStopBit)
/// If using MessageMode, the event character is set to carriage-return (\n)
/// </summary>
/// <param name="baudRate"></param>
/// <param name="parity"></param>
/// <param name="byteSize"></param>
/// <param name="stopBits"></param>
private void ConfigurePort(int baudRate, ParitySetting parity, byte byteSize, StopBitsSetting stopBits)
{
DCB dcb = new DCB() ;
dcb.BaudRate = (uint) baudRate ;
dcb.Parity = parity ;
dcb.ByteSize = byteSize;
dcb.StopBits = stopBits;
dcb.EvtChar = _activeReadMode == ReadMode.Message ? (sbyte)'\n' : (sbyte)0 ;
SetCommState (_portHandle, dcb);
}
/// <summary>
/// Sets COMM Port read timeout values
/// Hands off to the proper SetReadTimeOuts method based on the active read mode
/// </summary>
private void SetReadTimeOuts()
{
if (_activeReadMode == ReadMode.Message)
SetReadTimeOuts_MessageMode() ;
else
SetReadTimeOuts_CharacterMode() ;
}
/// <summary>
/// Sets COMM Port read timeout values
/// Using minimal timeout values because we don't try to read from the COMM port
/// until we are signaled that a carriage-return ('\n') has been recevied. As a
/// result we already know that we have all of the data so no need to wait to see what
/// comes.
/// </summary>
private void SetReadTimeOuts_MessageMode()
{
COMMTIMEOUTS timeOuts = new COMMTIMEOUTS();
timeOuts.ReadIntervalTimeout = 10 ;
timeOuts.ReadTotalTimeoutMultiplier = 0 ;
timeOuts.ReadTotalTimeoutConstant = 0 ;
timeOuts.WriteTotalTimeoutMultiplier = 0 ;
timeOuts.WriteTotalTimeoutConstant = 0 ;
SetCommTimeouts(_portHandle, timeOuts) ;
}
/// <summary>
/// Use long timeout multiplier so that we wait efficiently between GPS messages. The
/// interval timeout doesn't matter for us because we read the data one character at a
/// time in character mode.
/// </summary>
private void SetReadTimeOuts_CharacterMode()
{
COMMTIMEOUTS timeOuts = new COMMTIMEOUTS();
timeOuts.ReadIntervalTimeout = 0 ;
timeOuts.ReadTotalTimeoutMultiplier = 2000 ;
timeOuts.ReadTotalTimeoutConstant = 0 ;
timeOuts.WriteTotalTimeoutMultiplier = 0 ;
timeOuts.WriteTotalTimeoutConstant = 0 ;
SetCommTimeouts(_portHandle, timeOuts) ;
}
#endregion
#region Wrappers over Win32 Methods
/// <summary>
/// Wrapper method to simplify the opening of the COMM port
/// Port name must be of the form COMx: - the colon is required
/// </summary>
/// <param name="portName"></param>
/// <returns></returns>
private static uint OpenPort_Raw(string portName)
{
return CreateFile(portName, GENERIC_READ, 0, 0, OPEN_EXISTING, 0, IntPtr.Zero);
}
/// <summary>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -