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

в 8:23, , рубрики: gps, wince, Блог компании «ГК «СКАУТ»», метки: , ,

Введение

Всем привет!
В этой статье я хочу рассмотреть вопрос реализации доступа к данным GPS в устройствах на базе WindowsCE. При создании продукта СКАУТ-Навигатор, необходимо было разработать приложение, работающее как в WinСЕ версии 5.0, так и в WinCE версии 6.0, которое умеет получать данные NMEA с навигационного приемника, и записывать их в журнал.

Решение

Для работы с GPS в WinCE как версии 5.0, так и версии 6.0 проще всего использовать работу с COM портом. Найти в устройстве, какой COM-порт предоставляет данные GPS, можно при помощи программы: DeviceManager.
Работа с GPS в WinCE (C#)

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

Автор: scoutgps

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js