Работа с GPS в WinCE (C#)

от автора

Введение

Всем привет!
В этой статье я хочу рассмотреть вопрос реализации доступа к данным 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 строк.
Для этого можно:

  1. Написать свой парсер NMEA;
  2. Использовать SharpGps;
  3. Использовать NMEA-0183-2-0-Sentense-parser-builder (Тут статья разработчика на Хабре);
  4. Любой другой парсер.

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 она избавит.
Спасибо за внимание. Жду вопросов и замечаний в комментариях.

ссылка на оригинал статьи http://habrahabr.ru/company/scout/blog/157975/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *