Введение
Всем привет!
В этой статье я хочу рассмотреть вопрос реализации доступа к данным GPS в устройствах на базе WindowsCE. При создании продукта СКАУТ-Навигатор, необходимо было разработать приложение, работающее как в WinСЕ версии 5.0, так и в WinCE версии 6.0, которое умеет получать данные NMEA с навигационного приемника, и записывать их в журнал.
Решение
Для работы с GPS в WinCE как версии 5.0, так и версии 6.0 проще всего использовать работу с COM портом. Найти в устройстве, какой COM-порт предоставляет данные GPS, можно при помощи программы: DeviceManager.
Часто производители прошивок уже позаботились о том, чтобы COM портов GPS было два. Это позволяет развести ПО, которому требуется GPS и навигационное, чтобы они не боролись за доступ к COM-порту. Предположим, что COM порт мы будем использовать в монопольном доступе.
Чтобы получить данные NMEA (http://ru.wikipedia.org/wiki/NMEA_0183), нам нужно всего-то открыть COM порт, прочитать с него данные, потом закрыть COM порт. Что на C# выглядит так:
///<summary>
/// Чтение данных COM порта
///</summary>
///<param name="comPortName">Имя COM-порта</param>
///<param name="baudRate">Скоростьобмена</param>
private void ReadData(string comPortName,int baudRate)
{
var serialPort = newSerialPort(comPortName)
{
BaudRate = baudRate,
DataBits = 8,
Parity = Parity.None,
StopBits = StopBits.One,
RtsEnable = true
};
serialPort.Open();
//Чтение из COM-порта
//.....
var line=serialPort.ReadLine();
//.....
serialPort.Close();
}
Несмотря на то, что всё выглядит весьма тривиально, приведенный код часто не работает из-за ошибок доступа к COM-порту. (Например, часто возникает ошибка: «UnauthorizedAccessException: Access to the port is denied»).
Не будем расстраиваться, есть другой подход, который работает.
Замечательные люди из проекта OpenNetCf заботливо предоставляют исходные коды собственного SerialPort.
http://serial.codeplex.com/SourceControl/changeset/view/25883#435389
Добавляем в проект сборку OpenNetCf.IO.Serial
Класс работы с COM-портом GPS будет выглядеть так:
///<summary>
/// Класс работы с GPS COM-портом
///</summary>
public class GpsPort: IDisposable
{
private Port _serialPort;
private bool _disposed;
private readonly object _syncObject = new object();
public bool IsOpen { get; private set; }
///<summary>
///Порт чтения данных GPS
///</summary>
///<param name="serialPortName">Наименованиепорта</param>
///<param name="baudRate">Скоростьпорта</param>
public GpsPort(string serialPortName, int baudRate)
{
_serialPort = newPort(serialPortName, newDetailedPortSettings { BasicSettings = newBasicPortSettings { BaudRate = (BaudRates)baudRate }, EOFChar = 'n' });
_disposed = false;
}
public void Dispose()
{
if (!_disposed)
{
Close();
_serialPort = null;
_disposed = true;
}
GC.SuppressFinalize(this);
}
///<summary>
///Destructor
///</summary>
~GpsPort()
{
Dispose();
}
///<summary>
/// Открываем порт для чтения данных
///</summary>
public void Open()
{
try
{
_serialPort.Open();
_serialPort.DataReceived += SerialPortDataReceived;
IsOpen = true;
}
catch (Exception ex)
{
throw new ApplicationException("Could not open com port", ex);
}
}
///<summary>
/// Получение данных с порта
///</summary>
private void SerialPortDataReceived()
{
lock (_syncObject)
{
if (_serialPort == null || !_serialPort.IsOpen) return;
var realPortData = _serialPort.Input;
if (realPortData.Length == 0) return;
Debug.Write(Encoding.GetEncoding("ASCII").GetString(realPortData, 0, realPortData.Length));
}
}
///<summary>
///Закрытиепорта
///</summary>
public void Close()
{
IsOpen = false;
if (_serialPort != null)
if (_serialPort.IsOpen)
{
_serialPort.DataReceived -= SerialPortDataReceived;
_serialPort.Close();
}
}
}
Вметоде SerialPortDataReceived пишем, собственно, парсинг NMEA строк.
Для этого можно:
- Написать свой парсер NMEA;
- Использовать SharpGps;
- Использовать NMEA-0183-2-0-Sentense-parser-builder (Тут статья разработчика на Хабре);
- Любой другой парсер.
Sentenseparserbuilder я использовать не пробовал, а вот про SharpGps есть, что рассказать.
Сделаю небольшое отступление от темы. В библиотеке есть забавная ошибка в подсчете контрольной суммы, хотя многие мои коллеги считают, что это не ошибка, а логичное поведение. Но обо всём по порядку:
В протоколе NMEA 0183 (http://www.tronico.fi/OH6NT/docs/NMEA0183.pdf) контрольная сумма описывается как 2-значное 16-ричное число — контрольная XOR-сумма всех байт в строке между «$» и «*».
В SharpGps есть функция проверки корректности контрольной суммы в пакете:
private bool CheckSentence(string strSentence)
{
int iStart = strSentence.IndexOf('$');
int iEnd = strSentence.IndexOf('*');
//If start/stop isn't found it probably doesn't contain a checksum,
//or there is no checksum after *. In such cases just return true.
if (iStart >= iEnd || iEnd + 3 > strSentence.Length) return true;
byte result = 0;
for (int i = iStart + 1; i < iEnd; i++)
{
result ^= (byte)strSentence[i];
}
return (result.ToString("X") == strSentence.Substring(iEnd + 1, 2));
}
Эта функция великолепно работает, если навигационный приемник передает контрольную сумму в виде двух чисел (0x01, 0x02 и т.д.), как и заявлено в протоколе. Но любой идеальный код разбивается о реальность, в которой навигационные приемники передают пакеты с контрольной суммой, не добавляя ведущий ноль (0x1,0x2).
В работающем приложении получается, что часть пакетов отсеивается. При этом ощущение, что всё вроде бы работает. Но трек, хоть он и есть, но очень плохого качества.
Чтобы всё заработало, последнюю строчку можно переписать, хотя бы так:
var packCrc = byte.Parse(strSentence.Substring(iEnd + 1, 2),
System.Globalization.NumberStyles.AllowHexSpecifier);
return (result == packCrc);
С отступлением всё.
Для хранения навигационных данных было решено использовать SqlServer Compact Edition. Его очень просто интегрировать в приложение, и использовать в разработке. Описывать использование SqlServer Compact в данной статье я не планировал, если есть желание увидеть статью по использованию SqlServer Compact в приложениях на WinCe можете его обозначить в комментариях.
Заключение
В данной статье я привел решение проблемы доступа к GPS данным на WinCe устройствах, решение опробовано на навигаторах различных производителей (Prestigio, Texet, Shturmann, Mio) с разными версиями WinCE. Надеюсь что от части граблей подстерегающих вас на пути разработки под WinCE она избавит.
Спасибо за внимание. Жду вопросов и замечаний в комментариях.
Автор: scoutgps