Olexa's Logo

GPS PAGES
PHOTO GALLERY HOME

ПРОГРАММИРОВАНИЕ ИНТЕРФЕЙСА МЕЖДУ АППАРАТАМИ СПУТНИКОВОЙ НАВИГАЦИИ ФИРМЫ ГАРМИН (GARMIN) И КОМПЬЮТЕРАМИ НА ПЛАТФОРМЕ I86.

Copyright © 2002 by CYBER


We believed we'd catch the rainbow
Ride the wind to the sun
Sail away on ships of wonder…

R.Blackmore

Введение

В последнее время спутниковая навигация становится не только привилегией вояк и большого мореплавания. Все больше грибников, туристов и браконьеров используют эти портативные устройства определения месторасположения себя любимого на местности. Не избежал этой участи и я. После приобретения аппарата сразу встал вопрос: а как сохранять данные полевых съемок и как их использовать отдельно от аппарата. Поиск в Интернете и общение на различных форумах дали почти неутешительные результаты. Стандартом оказалось программа ОЗИ. Многое меня в ней не устраивало, и я решил разобраться с протоколом обмена навигатора с машиной. Скромные результаты своих исследований докладываю…

В качестве исходной информации я использовал документацию с официального сайта Garmin Corp., исходники программы G7 (огромная благодарность автору, программу эту я взял за основу, по крайней мере, все так логично написано, что я и менять особенно ничего не стал), для преобразования проекций и координат использовал электронный справочник EPSG (Европейская группа нефтяников-геодезистов – удивительно толково составленный документ, явно не америкосы писали), найденный на одноименном сайте, разрозненные данные из Интернета.

Для разработки я выбрал Delphi 6, поскольку Паскаль я наиболее хорошо знаю, и не пришлось тратить время на разработку оболочки программы. Все сделанное проверено на практике с использованием Windows ME .

По ходу изложения своих соображений я буду указывать место в фирменном руководстве Гармин.

GARMIN GPS Interface Specification
December 6, 1999
Drawing Number: 001-00063-00 Rev. 3
File Type: Microsoft Word 97
Archive File: 00100063.003

Сразу хочу сказать, что данная монография охватывает только вопрос обмена данными следующих типов: Треклоги (Журнал Кривой перемещения), Путевых Точек, Маршрутов и никак не освещает картографические возможности – заливку карт. Также мы опустим инженерные (если так можно выразиться) команды – манипуляцию с операционкой Навигатора, русификацией и пр.

Проблемы высшей геодезии мы тоже рассматривать не будем.

Является ли данный документ переводом фирменного руководства? Скорее нет. Я ставил перед собой задачу поделиться именно знаниями, связанными с написанием собственных приложений. Может, кому взбредет в голову написать надстройку на автолиспе для коннекта с Автокадом.

Понимать, как ЭТО работает, может и не жизненно важно, но приятно. Так что не скажу «Масдай Ози!!!», но ведь Линуху тоже сначала высмеивали…

Pdf - файл я сливал из Интернета в апреле 2002 года.
Прибор спутниковой навигации у меня Гармин Venture.

Данная статья не предусматривает никаких ограничений на использование программного кода в любых некоммерческих целях, правда, я хочу предупредить, что за все возможные неполадки с аппаратом спутниковой навигации и компьютером я ответственности не несу. Все эксперименты Вы предпринимаете на свой страх и риск. Как пишет тот же Гармин на заставке своего приемника – если Вы заблудились, – то это за Ваш счет.

Часть Первая. Основные положения.

Основные форматы данных, введенных Гармином для передачи данных.

(7.4. Basic GARMIN Data Types)

При разработке интерфейса фирма Гармин для своих Навигаторов ввела некоторое количество специфических форматов данных. Учитывая, что для примеров я использовал 32-разрядный Паскаль, встроенный в Delphi, следует сразу уточнить все виды переменных и констант во избежании недопонимания со стороны компилятора. Для переделки и разработки приложений на С нужно просто следовать стандартному С++ представлению данных, описанному в фирменном руководстве. В нем, по сути дела, приведен почти полностью заголовочный файл. Прежде чем начать описывать обмен данными между Навигатором (так будем называть GPS – приемник) и Компьютером, необходимо детально рассмотреть их.

1. Массив символов фиксированной длины.

CharArr : array [0..n-1] of char;

В отличие от принятых строк с нулем на конце, имеет заранее определенную длину.

2. Null – terminated string

Здесь далеко не все понятно. В Паскале это называться Pchar. Правда следует помнить, что и в С и в Паскале под NTS понимается, обычно, УКАЗАТЕЛЬ на строку с нулем на конце. В контексте Гармина следует понимать это как последовательность ANSI символов с нулем в качестве разделителя строк.

3. Byte

4. Word

5. Real

***********

6. LongWord

32-разрядное число без знака.

7. Boolean

Представляется как Байт. Если он отличен от нуля, то ИСТИНА, в противном случае ЛОЖЬ.

8. Semicircles

Специфический тип. В 180 градусах содержится 2**31 семицирклов (ну как назвать это по-русски иначе, я видел несколько переводов, неудачные все они). Для того чтобы перевести градусы в семицирклы, надо 2**31 поделить не 180 и умножить на количество градусов в интересующем нас угле. Введено для того, чтобы уйти от сложного для расшифровки типа Real (мне так кажется, хотя непонятно, как процессор гармина работает с плавающей точкой, может это и внутренний формат. Хотя для вычисления позиции нужно пересчитать массу тригонометрии, а там без плавающей точки никак. Не знаю, догадки одни…). При передаче угол, выраженный в семицирклах, представляется 32 разрядным целым со знаком (LongInt). Север и Восток обозначаются положительными числами. В одном градусе 11930464.7111111111 семицирклов.

9. Semicircle_Type

Основный тип представления координат для передачи между Навигатором и компьютером. Здесь Integer Паскалевский 32 разрядный, не путать с Сишным (ANSI C).

type
semicircle_type = record
Lat : integer; // широта в семицирклах
Lon : integer; // долгота в семицирклах
end;

10. Radian_Type

Второй тип записи координат. Широта и долгота дается в радианах. Как известно из школьного курса математики (Советский стандарт образования, в Штатах, говорят, это изучают в колледжах) в 180 градусах содержится ПИ радиан. Соответственно, один радиан равен приблизительно 57.295779513082322 градусов, а один градус равен 0.017453292519943296 радиан.

type
radian_Type = record
lat : Real; // широта в радианах
lon : Real; // долгота в радианах
end;

Протоколы

Garmin весь процесс обмена осуществляет с помощью трех видов протоколов. Протокол нижнего уровня – физический протокол. Это непосредственно операции с RS232 портом. Инициализация, отправление и получение через него данных. Потом идет Link протокол. Этот Протокол устанавливает правила формирования формат кадра данных, которые получает или посылает GPS. Протокол связи (назовем его так) также устанавливает правила и последовательности посылки кадров (пакетов) данных. Протокол связи отслеживает корректность и целостность передачи данных, не вдаваясь в содержание кадра. За содержимое кадра, Несущее осмысленную, Нужную Юзеру информацию, отвечает Application Протокол. Назовем его Протоколом Приложения. Не очень удачное название, но ничего другого в голову не идет.

Итак, обмен GPS с машиной строится на следующем принципе. Программа подготавливает данные, упаковывает их до формата кадра (пакета) по правилам, установленным Протоколом Приложения, Протокол Связи добавляет к кадру служебную информацию и передает Физическому Протоколу данные на отсылку. После чего возвращает Протоколу Связи служебную информацию о результатах передачи. Протокол Связи выясняет как там у нас отправленные данные (прошло/не прошло, сбой контрольный суммы и пр.). Если программа ожидала от GPS каких либо данных, Протокол Приложения извлекает их из принятого кадра.

Часть Вторая. Физический обмен между Устройствами

Физический Протокол

Как указано в фирменном руководстве, GPS использует для связи с компьютером последовательный порт. Garmin устанавливает следующие требования к параметрам, устанавливаемым при инициализации COM- порта.

Скорость передачи данных 9600 бод
Количество бит 8
Четность Нет
Стоповый бит 1

Данные установки требуются для обмена по фирменному Гарминовскому протоколу.

Здесь требуется сделать небольшое отступление. Прибор поддерживает несколько физических протоколов. Каждый из них требует своих настроек COM-порта. Поскольку на фирменный протокол нашлось наибольшее количество документов, я начал исследовать его. Для дальнейших экспериментов с аппаратом в его настройках необходимо установить в Навигаторе протокол garmin в меню настроек протокола.

Как инициализировать Порт

Windows использует для записи и чтения из порта созданный для этого файл. Рассмотрим инициализацию порта подробнее.

function AsyncInit(port: PChar): PChar;
var
fSuccess : bool; // результат инициализации порта
m : string; // временная текстовая переменная
begin
hCom: =Createfile(port,
GENERIC_READ+GENERIC_WRITE,
0,
nil,
OPEN_EXISTING,
0,
0);

{ hCom – дескриптор созданного файла. Он описываетя
в другом модуле как переменная типа дескриптор в секции Public.
Этот дескриптор будет использоваться в других процедурах
и функциях для установки параметров порта, чтения, записи
и закрытия порта. Прочитать про функцию CreateFile
(если интересно) можно в хелпе MS SDK. Изложено подробно,
но бестолково. Для начала достаточно знать, что это работает...}


if (hCom = INVALID_HANDLE_VALUE) then begin
m:='Порт открыт неуспешно';
end;

{Теперь проверим, окрылся ли порт. Если hCom отрицательный,
то ясно, что-то не так. Скорее всего, нужно задать другой порт.
Вопрос, кстати сказать, довольно сложный. Все зависит
от установленного оборудования (модемы и пр.). Теперь сохраним
первоначальные установки порта.Это нужно для того, чтобы перед
закрытием программы восстановить эти установки. Конечно,
закрывать программу первое время придется при помощи трех клавиш,
так что до восстановления дело дойдет не скоро.}

fSuccess := GetCommState(hCom, dcb_);


BaudRate := dcb_.BaudRate;
ByteSize := dcb_.ByteSize;
Parity := dcb_.Parity;
StopBits := dcb_.StopBits;

if not(fSuccess) then begin
m:='Порт открыт неуспешно';
end;


AsyncInit:=PChar(m);
end;

Если порт открыт успешно, то теперь надо установить его параметры. Кстати, теперь станет ясно, что такое dcb_. Это, как становится ясно после прочтения SDK – хелпа, такая структура, в которой содержатся все установки для устройства коммуникации, последовательного порта в частности. Из всего многообразия нам нужны только четыре параметра – скорость, размер, четность и количество стоповых битов. Перемененную dcb_ , которая имеет тип DCB, опишем как глобальную переменную где угодно, лишь бы она сохранялась на время всего выполнения программы. Да, забыл сказать, я все операции с инициализацией и установками порта сделал функциями, результатом которых является текстовая информация, чтобы сразу можно было вставлять в Messagebox и видеть результаты выполнения.

function AsyncSet(wBaudRate,wByteSize,wStopBits,wParity : Integer): PChar;
var
fSuccess : bool;
m : string;
begin
dcb_.BaudRate := wBaudRate;
dcb_.ByteSize := wByteSize;
dcb_.StopBits := wStopBits;
dcb_.Parity := wParity;

fSuccess := SetCommState(hCom, dcb_);

GetCommState(hCom,DCB_);

if not(fSuccess) then begin
m:='Ошибка установки параметров порта';
end;

AsyncSet:=PChar(m);
end;

Ну, здесь все прозрачно. Теперь необходимо рассмотреть непосредственно ввод-вывод.

Функция AsyncOut непосредственно записывает в инициализированный порт массив из «count» байт. То есть мы формируем пакет, узнаем его длину, и вызываем функцию записи в порт.

function AsyncOut(msg : array of Byte; count: DWord): PChar;
var
bytes : DWord;
OvLapp : POverlapped;
res : Byte ;
s : string;
begin
OvLapp:=nil;
WriteFile(hCom, msg, count, bytes,OvLapp);
if res <> 0 then begin
str(bytes,s);
s:= 'Запись в порт прошла успешно. Записано '+ s + ' Байт';
AsyncOut:= PChar(s);
end else begin
AsyncOut:='Ошибка записи в порт ';
end;
end;

Функция ввода из порта читает по одному байту. Объясняется это так. Когда мы посылаем данные, то нам заранее известен размер кадра. А вот принимаемый кадр имеет неизвестную длину. Так что читать приходится по одному байту.

function AsyncIn :byte;
var
Bytes : DWord;
c : byte;
OvLapp : POverlapped;
begin
OvLapp:=nil;
ReadFile(hCom, c, 1, bytes,OvLapp);
AsyncIn :=c;
end;

Функция AsyncInStat осуществляет проверку наличия во входном буфере последовательного порта какой – либо информации. Как будет описано ниже, размер кадра можно будет выяснить уже после прочтения первых его байтов. Но для того, чтобы отловить ошибки ввода/вывода необходимо проверять, сколько байтов принял компьютер от GPS.

Функция возвращает следующие значения:

True - байты еще есть
False - буфер пуст

function AsyncInStat : boolean;
var
commstat : PComStat;
along : Cardinal;
begin
new(commStat);
ClearCommError(hCom, along, CommStat);
if(CommStat^.cbInQue > 0) then
AsyncInStat := true
else
AsyncInStat := false;
freemem(commstat);
end;

Сразу хочу оговориться, что мы пока не будем детально рассматривать такие вещи, как время ожидания ответа. Вопрос, на самом деле, нужный. Опыт показывает, что плохой контакт в гнезде GPS имеет место быть постоянно.

Вообще, я бы советовал обойтись пока этими двумя функциями и не связываться с библиотеками визуальных компонентов, во множестве представленных в Интернете и на CD. Опыт показывает, что все они работают или некорректно (не часто, но бывает), или, что особенно раздражает, постоянно теряются. Переустановил ты систему, поставил заново Delphi, а откуда взялась та библиотека VCL, ну никак не вспомнить. То сайт переехал, то пластинка запиленная оказалась и пр. и пр.

Тем более, наша задача получить предельно четкие представления обо всем механизьме (как говорил Выбегалло) передачи данных.

Физический протокол связи Гармин обозначает как P000.

Теперь, если все заработало, можно переходить непосредственно к Протоколу Связи.

Часть Третья. Логический обмен между Устройствами

Протокол Связи (Link Protocol)

(4. Link Protocols фирменного руководства)

Необходимое замечание.
Совершенно непонятно, почему Гармин принял стандартом своего описания не шестнадцатеричное, а десятичное представление данных. С самого начала я сам путаюсь, так что я буду явным способом указывать тип представления информации.

Формат Пакета (Кадра).

Для начала приведем оригинал.

Byte Number

Byte Description

Notes

0 Data Link Escape ASCII DLE character (16 decimal)
1 Packet ID Identifies the type of packet
2 Size of Packet Data Number of bytes of packet data (bytes 3 to n-4)
3 to n-4 Packet Data 0 to 255 bytes
n-3 Checksum 2's complement of the sum of all bytes from byte 1 to byte n-4
n-2 Data Link Escape ASCII DLE character (16 decimal)
n-1 End of Text ASCII ETX character (3 decimal)

Это описание формата кадра, приведенное в официальном руководстве. Переведем.

Номер байта

Описание Байта

Примечание

0 Data Link Escape
Не переводится - старинный управляющий ASCII символ, корни его уходят в дремучее IBMовское прошлое.
ASCII DLE десятичное 16, шестнадцатеричное 10
1 Packet ID – Идентификатор Пакета (кадра) Описывает тип передаваемого кадра
2 Размер кадра Количество байтов с третьего по n-4. Где n –длина пакета.
С 3 по n-4 Непосредственно данные. 0 to 255 bytes. Вот здесь надо внести ясность. Максимальная длина блока данных пакета (кадра) может быть от 0 до 255, как уверяют Гарминовские инженера. На самом деле ВЕСЬ пакет должен быть не длиннее 256 байт. Наверно, имеется в виду, что байты в блоке могут быть равными от 00 до FF
n-3 Контрольная сумма Двоичное дополнение суммы всех байт с 1 по n-4.
n-2 Data Link Escape ASCII DLE см выше
n-1 End of Text – тоже старинный управляющий символ. Обозначает конец текста. ASCII ETX десятичное 3, шестнадцатеричное тоже 3 (странно, но факт)

Все навигаторы фирмы Гармин (доступные Российскому гражданскому потребителю) поддерживают формат кадра при обмене по фирменному протоколу. Кадр делится на заголовок. В руководстве он назван “Header”. Собственно данные и постфикс (“Trailer”).

Заголовок содержит символ DLE, Идентификатор Пакета и размер Пакета. Всего три байта. Постфикс содержит Контрольную сумму, символ DLE, и символ ETX. Тоже три байта. Таким образом, символ DLE ограничивает Пакет.

Подробнее стоит остановиться на DLE символе. Очевидно, что и в теле данных и в байте контрольной суммы может содержаться байт, равный 16 dec. Гармин предлагает способ, как отличить DLE от числа 16 (байта имеющего значение hex 10).

Для этого каждый байт, Содержащий значение 16 dec и не являющийся управляющим символом, дублируется еще одним байтом (сразу за ним вставляется еще один байт, равный 16 dec). Причем добавленные байты не учитываются в размере пакета и не подсчитываются в контрольной сумме.

Напомню, как подсчитать контрольную сумму.

Необходимо объявить переменную типа байт. Присвоить ей значение 0 и начать вычитать из этой переменной побайтно всю последовательность данных, которую необходимо защитить контрольной суммой. Пример вычисления контрольной суммы смотри в описании функции SendGarminMessage ниже.

Следует отметить традиционную для америкосов путаницу с терминами. Pid ом они называют как Идентификатор пакета так и Идентификатор Протокола. Так что не следует пугаться, обнаружив в пакете два Пида. Один из них - идентификатор Пакета, второй - протокола. То есть, первый говорит, что передавать НАДО, а второй ЧТО собственно будем передавать.

Проверка корректности физической передачи данных

(4.1.3. ACK/NAK Handshaking фирменного руководства)

При обмене данными между Навигатором и компьютером необходимо проверять корректность переданных данных и следить за готовностью устройства к приему данных. Хотя мы связываемся с навигатором при помощи асинхронного физического протокола, возникают случаи, когда одно из устройств не может принять данные.

Для устранения этих неприятностей применяется хитрый метод, который называется Handshaking, рукопожатие так сказать.

Для осуществления этой процедуры служат специальные пакеты ACK и NAK. Пакеты представляют собой обыкновенные пакеты обмена, в которых Идентификатор Пакета равен символу ACK или NAK. Символ ACK равен 6 dec ASCII, а символ NAK равен 21 dec. В Поле Данных оба пакета содержат 8 битные Integer числа (кстати, почему Integer, ведь с самого начала говорилось о том, что Идентификатор Пакета имеет тип байт), которые указывают, ответом на какой пакет дается ответ.

Например, посылаем мы навигатору пакет с Идентификатором XX, а он нам дает в ответ пакет ACK, в котором в поле данных содержится XX. И сразу становится ясным, что все в порядке – данные дошли и успешно декодированы. Сразу можно проверить и то, насколько точно навигатор понял команду. Схема рукопожатия отлавливает только ошибки нижнего уровня (ошибки передачи) и не вдается в детали.

Гармин говорит, что некоторые модели в поле данных могут возвращать не один байт, а два. И рекомендует не обращать внимание на второй байт.

Некоторые модели навигаторов могут выдавать пакеты NAK, если передача логически связанной цепочки пакетов была приостановлена на длительное время. В этом случае пакеты NAK выдаются с периодичностью несколько секунд. Могу сказать точно, что Venture не пользуется таким методом.

Основной Протокол Связи L001

(4.2. L001 – Link Protocol 1)

Все навигаторы поддерживают минимальный набор Идентификаторов Пакетов Основного протокола связи. Что это такое?

Приведу списочек.

const
Pid_Command_Data = 10;
Pid_Xfer_Cmplt = 12;
Pid_Date_Time_Data = 14;
Pid_Position_Data = 17;
Pid_Prx_Wpt_Data = 19;
Pid_Records = 27;
Pid_Rte_Hdr = 29;
Pid_Rte_Wpt_Data = 30;
Pid_Almanac_Data = 31;
Pid_Trk_Data = 34;
Pid_Wpt_Data = 35;
Pid_Pvt_Data = 51;
Pid_Rte_Link_Data = 98;
Pid_Trk_Hdr = 99;
end;

Пакеты могут быть командными и информационными. Командные пакеты выделены в отдельную группу и отнесены к протоколу Связи, так как они управляют потоками данных и инициализируют или останавливают передачу.

Для начала нам надо знать только три Идентификатора. Это

Pid_Command_Data = 10;

Идентификатор указывает, что в пакете будет содержаться команда Устройству. Например, пакет, имеющий Идентификатор 10 dec и 06 dec в поле данных и посланный Навигатору, будет указывать на то, что Компьютер просит Навигатора начать передачу треклога.

Pid_Xfer_Cmplt = 12;

Пакет с таким идентификатором будет означать, что все данные переданы.

Pid_Records = 27;

Пакет с таким идентификатором будет содержать в поле данных количество записей, подготовленных для передачи, соответственно, принимающее устройство будет планировать их размещение и обработку.

Остальные идентификаторы дают информацию о типе передаваемых данных.

Существует также дополнительный Протокол, имеющий расширенные возможности, но он поддерживается не всеми Навигаторами. Рассматривать его здесь не будем, отметим только, что в списке расширенного протокола представлены еще и «инженерные» команды, предназначенные для управления операционкой Навигатора. Достоверной информации об этом нет и экспериментов не проводилось (Аппарат жалко, вспоминается программирование напрямую видеокарты, сжег тогда я строчник монитора…).

Формирование Пакета (кадра)

Теперь рассмотрим на примере, как подготовить данные для передачи навигатору и отослать их. Будем считать, что данные сформированы другой процедурой и к ним уже добавлен Идентификатор Пакета и длина данных. В примере показывается, как вычислить контрольную сумму.

procedure SendGarminMessage(msg:array of byte;bytes:integer);
var
i : Integer; // переменная цикла
pos : Integer;
csum : byte;
SerialMessageOut: array of byte; // буфер ввода/вывода
begin
pos := 0; // указывает на обрабатываемый байт в кадре
Csum:= 0; // контрольная сумма

// Первым байтом устанавливаем тот самый DLE
SerialMessageOut[pos] := $10;

// Передаем из буфера сообщения в буфер, который
// непосредственно будет передан Навигатору, дублирую
// попутно все байты, равные $10

for i:= 0 to bytes - 1 do begin
Inc(pos);
SerialMessageOut[pos]:= msg[i];
csum := csum - msg[i];
if msg[i] = $10 then begin
Inc(pos);
SerialMessageOut[pos] := $10;
end;
end;

// Добавим контрольную сумму
Inc(pos);
SerialMessageOut[pos]:= csum;

// Если контрольная сумма равна $10 то тоже продублируем
if (csum = $10) then begin
Inc(pos);
SerialMessageOut[pos] := $10;
end;

// Теперь добавим Постфикс
Inc(pos);
SerialMessageOut[pos]:=$10;
Inc(pos);
SerialMessageOut[pos]:=$03;

AsyncOut(serialMessageOut, pos+1);
End;

Как видно из примера, мы начали сборку пакета с символа DLE, потом добавили данные Пакета, с уже указанным Идентификатором пакета и длиной. Попутно мы подсчитываем контрольную сумму и дублируем байты данных, равные символу DLE. После чего к Пакету добавлен Постфикс и вызвана функция асинхронного вывода в порт. Функции AsyncOut передается как аргумент сформированный пакет и его физическая длина (не путать с длиной данных).

Прием и декодировка Пакета (кадра)

Прием и декодировка пакета осуществляется несколько сложнее. Здесь мы должны учитывать некоторые тонкости.

Длина пакета, получаемого от навигатора, заранее не определена. Следовательно, придется читать из буфера порта побайтно всю информацию, помещенную туда навигатором, попутно анализируя ее и отделяя служебную информацию от пользовательской.

Это можно сделать, только анализируя входной поток и выделяя в нем символы DLE и EXT, которые следуют один за другим. Как говорилось выше, все символы DLE, находящиеся в пользовательском блоке пакета всегда дублируются во избежании путаницы.

Рассмотрим последовательность байт во входном потоке (xx обозначает любой байт, отличный от 03 hex – EXT и 10 hex – DLE).

…xx xx xx xx 10 03 xx xx…

Очевидно, это конец пакета, дальше читать из буфера не имеет смысла.

…xx xx xx xx 10 10 10 03 xx xx…

В данном примере контрольная сумма равна 10 hex, значит, навигатор ее продублирует перед пересылкой. (Контрольная сумма идет перед Постфиксом).

…xx xx xx xx 10 10 03 xx xx…

Если во входном потоке присутствует такая последовательность, то это означает, что приняты пользовательские данные (один 10 hex мы удалим при обработке) и необходимо читать данные дальше.

Также возникает вопрос сбоя передачи. Эту задачу мы решим примитивно просто. Если Навигатор после посланной ему команды на передачу данных не ответил в течение 3 секунд (если 3 секунды Вам не нравится, задайте 5), то попытки прочитать из буфера порта прекращаются и вступает в действие процедура обработки ошибок. Понять, что Навигатор не ответил очень просто – в буфере порта не появится никаких данных. Обработать ошибку можно разными способами. Можно послать команду еще раз, считая количество повторов. При определенном количестве неудачных попыток можно констатировать, что связи нет совсем, либо навигатор не включен и пр. Тогда программа должна предпринять действия по прекращению выполнения команды пользователя и выдать ему сообщения. Для простоты понимания я исключил из примера анализ сбоев. Оставил только время ожидания.

Контрольная сумма тоже требует определенного внимания. После того, как достигнут конец пакета, необходимо проверить принятые данные.

Если контрольные суммы, полученная от навигатора и вычисленная программой совпадают, то нужно выдать навигатору команду ACK. Причем в команду ACK необходимо подставить Идентификатор Пакета, на который дается ответ. Например, если мы принимали точку треклога и все было в порядке, то ACK должен содержать Идентификатор Пакета приема одной записи треклога.

Если контрольная сумма неверная, то нужно послать Навигатору пакет NAK и повторить чтение и обработку буфера порта.

Как только все встало на свои места, данные приняты и проверены, из пакета выделена пользовательская информация, можно вызвать процедуру обработки пользовательских данных. Рассматривать такую обработку пока не будем.

Теперь пример:

procedure GetGarminMessage(exit_on_error: Integer);
var
s : string; // текстовые переменные для вывода
s1 : String[3]; // сообщений
i, // переменная цикла
fails : Integer; // количество попыток (сбоев порта)
GPSchecksum : Boolean; // Правильна ли контрольная сумма
t : TDateTime; // Временная переменная ожидания
Last : Byte; // Предыдущий прочитанный байт
InByte : Byte; // Только что прочитанный байт
MSGLen : Integer; // Длина сообщения
EndOfRec : Boolean; // Достигнут конец сообщения
pos : Integer; // указатель на байт во входной очереди
begin
Garmin_NAKS_Sent := 0;
pos := 0;
MSGLen := 0;
SerialMsgLen := 0;
EndOfRec := false;
last := 0;
t := Now;
COMtimeout := 0;
fails := 0;

repeat
if ((Now-t)*24*60*60 > TIMEOUT) then begin

{Ну, здесь специфические Борландовские дела с датой и временем,
функция Now дает дату-время в дробных днях. Для получения
времени в секундах нужно умножить значение на количество секунд
в сутках}

Inc(fails);
if (fails>3) then begin
Messagebox(handle,'GPS не отвечает','Ошибка коммуникации',64);
COMtimeout:=1;
t:=Now;
exit;
end;

// SendGarminMessage(LastGarminGPSMsg,LastGarminLen,'Re-send');
end else begin
COMtimeout:=0;
while (AsyncInStat) do begin
// есть ли чего еще в буфере
InByte:=AsyncIn; // читаем один байт из буфера< BR>
if ((InByte = $03) AND (last = $10)) then
// предположительно достигнут конец записи
EndOfRec := true
else
EndOfRec := false;< BR>
if ((InByte = $10) AND (last = $10)) then
// пропускаем дублирующ $10
last := 0
else begin
last := InByte;
SerialMessageIN[pos] := InByte;
Inc(pos);
Inc(MSGLen);
end;

if ((SerialMessageIN[pos-1] = $03)
AND (SerialMessageIN[pos-2] = $10)
AND EndOfRec) then begin
// достигнут конец записи
GPSchecksum:=checksum(SerialMessageIN,MSGLen);
if not(GPSchecksum) then begin
// SendGarminMessage(nak1,4,'NAK'); посылаем NAK
// goto повторный вход в процедуру;
end else begin
ack1[2]:=SerialMessageIN[1]; // Сформируем ACK
if NOT(SerialMessageIN[1]=$06) then begin
// Не отвечаем на ACK Принятый от GPSa
SendGarminMessage(ack1,4, 'ACK');

{… Здесь мы должны вызвать процедуру обработки данных
из принятого от Навигатора пакета. Процедура зависит
от типа принятого пакета и распределяет данные
для использования программой. Например, точки треклога
должны быть записаны в какой то временный массив,
чтобы потом быть сохраненными в файл, либо отображены на экране…}

end;
end; // if not(GPSchecksum) else begin
Exit;
end; //if ((SerialMessageIN[pos-1] = $03) AND
// (SerialMessageIN[pos-2] = $10) AND
// EndOfRec) then
end; // while (AsyncInStat) do
end; // if ((Now-t)*24*60*60 > TIMEOUT) else begin
Until false; // циклим чтение из буфера
end;

Часть Четвертая. Как заставить Навигатор и Компьютер обмениваться данными

Протокол Приложения

Как говорилось ранее, протоколы – это правила, по которым формируются данные для передачи. Протокол Приложения – это правила, по которым формируются записи для передачи данных между компьютером и навигатором. Я применяю термин «запись», так как он наиболее полно соответствует действительности. Ведь что собой представляет, например, треклог? Несомненно, что это последовательность однотипных структур данных, состоящих из определенного набора полей – широты, долготы, высоты и некоторой другой информации. Что это, если не «запись»?

Протокол Приложения еще определяет последовательность обмена пакетами при выполнении какой-либо логически законченной операции. Например, перекачки активного треклога из навигатора в компьютер.

Надо заметить, что модельный ряд Гармина достаточно широк и Протоколы Приложения для каждого семейства моделей различаются. Но принцип остается одинаковый для всех.

Стандартные начальные и завершающие пакеты

(5.3. Standard Beginning and Ending Packets)

Большинство операций по передаче данных представляют собой пересылку некоторого количества однотипных записей. Правило пересылки таких массивов следующее:

Если мы хотим принять из Навигатора какие либо данные, мы посылаем ему запрос на передачу. Для этого мы формируем пакет с идентификатором Pid_Command, куда включаем Идентификатор Протокола Приложения, в котором закодирована команда Навигатору. Иными словами, Навигатору говорится, чего, собственно, мы от него хотим. Например, мы хотим, чтобы он начал передачу треклога. Пакет, соответственно будет такой:

trk1 : TByteArr3 = ($0a,$02,$06,$00)

Первый байт - Pid_Command. Говорит навигатору о том, что это – команда.

Второй байт – длина данных в пакете (опа, а ведь заложено куча возможностей, команд может быть не 255, а значительно больше).

Третий байт – команда Cmnd_Transfer_Trk. Указывает Навигатору, что надо начать передачу данных треклога.

Ноль.

Заголовок, контрольная сумма и постфикс опущены для ясности. Данные приведены в Hex.

Передающее устройство в ответ на команду отправить данные выдает пакет Pid_Records, в котором содержится информация о количестве передаваемых записей. Получив сигнал ACK, устройство начинает последовательную передачу пакетов с пользовательскими данными. После передачи одного пакета с записью (точки треклога, например), Устройство должно остановиться и подождать ответа ACK, после чего выдает очередной пакет с данными. Навигатор это делает сам, при написании программы мы должны это помнить и при передаче Навигатору данных должны дожидаться от него ответа. Соответственно, приводится проверка контрольной суммы и времени ожидания. После того, как все записи переданы, Передающее устройство выдает пакет Pid_Xfer_Cmplt. Этот пакет говорит о том, что достигнут конец данных (передача закончена). Одновременно на дисплее Навигатора появится надпись Transfer Complete – Передача Выполнена. Дальнейшая задача программы - расшифровать данные и использовать для нужд пользователя.

Перед тем, как рассматривать детально Протокол Приложения, желательно собрать простенькую программку обмена с Навигатором. Например, начать с перекачки Треклога из Навигатора. Если Вы поняли, что написано выше, то с этого место можно начать формировать рабочую программу. Она будет выполнять осмысленные действия, результаты которых можно будет увидеть на экране.

Протокол команд. Инициализация и завершение передачи.

(6.3.1. A010 – Device Command Protocol 1)

Для того чтобы устройство начало передавать данные надо дать ему команду. Список команд, поддерживаемый всеми навигаторами, называется Протоколом А010. Команда выдается при помощи пакета, имеющего Идентификатор Pid_Command. После него в пакет включены непосредственно команды. Их назначение вполне понятно.

Cmnd_Abort_Transfer := 0; // abort current transfer
Cmnd_Transfer_Alm := 1; // transfer almanac
Cmnd_Transfer_Posn := 2; // transfer position
Cmnd_Transfer_Prx := 3; // transfer proximity waypoints
Cmnd_Transfer_Rte := 4; // transfer routes
Cmnd_Transfer_Time := 5; // transfer time
Cmnd_Transfer_Trk := 6; // transfer track log
Cmnd_Transfer_Wpt := 7; // transfer waypoints
Cmnd_Turn_Off_Pwr := 8; // turn off power
Cmnd_Start_Pvt_Data := 49;// start transmitting PVT data
Cmnd_Stop_Pvt_Data := 50;// stop transmitting PVT data

Как и в случае с протоколом Связи, здесь имеется дополнительный набор команд. Они описываются в Протоколе А011. Протокол А010 поддерживается всеми навигаторами. Кстати, существует несколько недокументированных команд, совершенно не нужных, но интересных. Например, команда Передать Содержимое экрана Навигатора. Описывать не буду, дабы не вводить в искус.

Осваивать программирование команд советую начать с программного выключения Навигатора. Радость просто детская, когда после нескольких сотен попыток он, наконец – то выключится. Потом можно перейти к команде передачи треклога, после чего все остальные будут доставлять проблемы только с расшифровкой и интерпретацией (осознанием данных).

Я, например, начинал с того, что сливал из Навигатора треклог, записанный на балконе.

Часть Пятая. Протоколы Приложения. Непосредственно данные.

Формат передаваемых данных условно можно поделить на две группы:

Одиночные. Передаются данные типа модели Навигатора, версии софта, залитого в аппарат и пр.

Цепочки. Последовательность однотипных пакетов данных. Как говорилось выше, в данном случае наблюдается полная аналогия с последовательностью записей базы данных. В этом случае формат данных заранее предопределен и программа знает, как в пакете упакованы поля одной записи.

Прежде чем начать разбираться с форматами данных, передающихся между Устройствами, нужно обсудить так называемую совместимость протоколов. Исторически сложилось, что разные семейства Навигаторов (разговор, естесствно, идет только о Гармине) пользуются своими форматами упаковки данных, построенных по общей для каждого типа форматов схеме. Например, точка треклога должна содержать обязательно (как минимум) два параметра - широту и долготу. Другие модификации этого формата могут содержать дополнительные поля, причем поля в записи могут располагаться в разной очередности.

Нельзя забывать, что прогресс не стоит на месте и появляются новые модели Навигаторов. Соответственно, нельзя заранее предугадать, как повернется процесс разработки Навигаторов, имеющих все более расширенные возможности. По этому с уверенностью можно говорить, что будут добавляться новые разновидности форматов упаковки данных.

Естественно, что если Вы будете делать программу для себя, вы заранее выберете форматы данных, конкретно с которыми работает Ваш Навигатор. Но если планируется передавать программный продукт на сторону, необходимо предусмотреть Совместимость Протоколов.

Все гарминовские навигаторы условно можно разделить на определенные группы-семейства, которые пользуются своим набором форматов данных. В принципе, все протоколы (форматы) одного типа разных семейств похожи. И методика расшифровки строится по одной схеме для всех разновидностей.

Перед началом обмена между Устройствами необходимо вызвать из Навигатора так называемый The Protocol Capability Protocol. Вот ведь могучий американский язык, тфу.

Определение модели Навигатора и Протокол совместимости протоколов…

(6.1. A000 – Product Data Protocol)
( 6.1.1. Product_Data_Type)
(6.2. A001 – Protocol Capability Protocol)

Перед тем, как начинать обмен данными между Навигатором и Компьютером необходимо выдать навигатору команду Pid_Product_Rqst. Навигатор должен ответить пакетом Pid_Product_Data. В этом пакете содержится информация о модели Навигатора, версии программного обеспечения, залитого в него.

Формат записи привожу:

TProduct_Data_Type = record
product_ID : Word;
software_version : Word;
product_description : Pchar;
end;

Где Product_ID –идентификатор продукта, так сказать, модели Навигатора, 16 – разрядное целое. Тут опять у меня возникает путаница, обозначил я это Word”ом и все дела. Версия программного обеспечения тоже представлена двухбайтовым числом. Для получения номера версии необходимо его поделить на 100. То есть, если мы получили 311 dec, то, соответственно, в Навигатор залит софт версии 3.11. После следуют Null-Terminated строки, в которой дано текстовое описание Навигатора. В официальном руководстве пишется, что строк может быть несколько.

Вообще, вызвать эту информацию полезно не только из соображений совместимости протоколов, но и для проверки установки связи с Навигатором, То есть после инициализации Порта, можно сразу отобразить окно типа About, в котором написать о том, что Навигатор модели «тыры-пыры» готов к передаче данных и поддерживает протоколы – и списочек. В конце, естественно, имя себя любимого и копирайт и ТМ и еще чего-нибудь для солидности. Это так положено. Как у Микрософта. Глючит, еле работает, а копирайтов и предупреждений о Пираси и Лав Протектед море.

Рис. 1
Вот что отвечает Вентура на запрос о модели.

Приведу пример, как распознать данные, содержащиеся в пакете. Предполагается, что Навигатор успешно обработал команду и поместил в SerialMessageIn пакет.

В официальном руководстве допущена ну если не неточность, так некоторая недообъясненность. Там говорится, что после Идентификатора модели и номера версии софта следует несколько Null-terminated string. Согласен. Но в Паскале NTS это УКАЗАТЕЛЬ на эту строку. Так что приходится исхитряться, отлавливая эти самые нули, перебирая строку побайтно. Ведь, если передать напрямую данные в переменную Pchar (что, кстати, само по себе не так просто, требуется промежуточный массив из Char), то можно будет видеть только строку до ПЕРВОГО НУЛЯ. Так что я просто перебираю остаток данных после вычленения двух двухбайтных чисел, заменяя нули переменной skip:= chr(10)+chr(13); (конец строки + перевод каретки). Для этого использую смещение массива SerialMessageIn, состоящее из суммы 3 байта префикса пакета и 4 байта данных (Идентификатор Навигатора и номер софта). Приведенный пример работает на функцию messagebox, если необходимо сохранить строки для дальнейшего использования, нужно несколько изменить пример, введя, скажем, динамический массив из Pchar. Решение, конечно, не самое красивое, мне важна прозрачность и понятность примера.

function TGetDataForm.doID:boolean;
type
TBuff = array[0..255] of byte;
PBuff = ^TBuff;
var
buffer : PBuff;
i : integer;
p : POINTER;
s : string;
s1 : string;
dd : ^TProduct_Data_Type ;
begin
new(buffer);
getMem(p,256);
new(dd);
p:=buffer;
dd:=p;

for i:=0 to 3 do begin
buffer^[i]:=SerialMessageIn[i+3];
end;

S:='';

for i:=7 to serialMessagein[2]-3 do begin
if SerialMessageIn[i] = 0 then
s:=s+skip
else
s:=s+Chr(SerialMessageIn[i]);
end;

s:=s+skip;

str(dd^.product_ID,s1);
s:=s+'Идентификатор Навигатора - '+s1+skip;

str((dd^.software_version/100):3:2,s1);
s:=s+'Версия софта - '+s1+skip;

messagebox(handle,PChar(s),'Отладка...',64);
end;

После пакета с названием и версией софта Навигатор сразу автоматически выдаст так называемый пакет Pid_Protocol_Array. То есть может выдать, а может и нет, в зависимости от модели. Вообще, существует таблица совместимости, в которой ясно указано, какие протоколы и форматы данных поддерживает Навигатор. Мне кажется, что все же проверить все это стоит. Обновленные прошивки появляются довольно часто, соответственно таблица может устареть. Потом наши доморощенные Ньютоны и Платоны (не побоимся этой цитаты из Классика), могут самопально модифицировать код при русификации и пр. Потом, честно говоря, мне было лень переформатировать эту таблицу из С++ в Паскаль, написал я простенькую функцию.

В поле данных этого пакета закодированы все типы протоколов и форматов данных, поддерживаемых данным аппаратом.

Каждый протокол и формат закодирован трехбайтовой записью, где первый байт – символ, обозначающий тип протокола, а переменная типа word его номер. Например, P000 – физический протокол связи. «000» в данном случае десятичное число (вот опять путаница началась с hex и dec).

В официальном фирменном руководстве приводится здоровенная таблица соответствия моделей и протоколов (и Link и Application). Не буду его копировать.

(8.2. GPS Product Protocol Capabilities)

D300 и D301 - Формат данных записи Треклога (Журнала Перемещения).

(6.6. Track Log Transfer Protocol)

Все Навигаторы, за исключением специально оговоренных случаев, используют следующий формат записи для каждой точки журнала.

Type
TD300_Trk_Point_Type = record
Posn : TSemicircle; // position
Time : longword; // time
new_trk : boolean; // new track segment?
end;

Где

Posn - координаты точки, выраженные в виде записи Tsemicircle (см выше).

Time – время записи точки, выражается в секундах, прошедших с новогодней ночи 89 года. Следует помнить, что все данные, имеющие тип времени, имеют отсчет относительно Гринвича. Так что не стоит пугаться трехчасового сдвига. А еще лучше сразу предусмотреть в программе сдвиг на поясное время. Кстати, какое время не ставь в Навигаторе. Все равно все данные временного типа будут передаваться по Гринвичу. Кстати, я так до конца и не понял: в руководстве говорится о том, что разные модели имеют различные нули даты-времени. По этому, после расшифровки этого поля, проверьте правильность установки часов навигатора и сравните полученные данные с фактическим числом месяца и временем.

New_trk – флажок, показывающий, что запись треклога прерывалась (потеря приема и пр.).

Как раскодировать данные, полученные от навигатора? Приведу пример:

function TGetDataForm.doTrack:boolean;
type
TBuff = array[0..255] of byte;
PBuff = ^TBuff;
var
D300 : ^TD300_Trk_Point_Type;
buffer : PBuff;
i : integer;
p : POINTER;
Lat, Lon, TimeRec : string;
s : string;
begin
new(buffer);
getMem(p,256);
new(D300);

p:=buffer;
D300:=p;

for i:=0 to 253 do
buffer^[i]:=SerialMessageIn[i+3];

str(D300^.posn.lat/Convert:14:10,lat);
str(D300^.posn.lon/convert:14:10,lon);
str(D300^.time:10,TimeRec);

s:= 'Широта ' +lat + skip +
'Долгота ' +lon + skip +
'Время записи' + TimeRec;

messagebox(handle,PChar(s),'',64);
freemem(p);
end;

Полученный от функции ввода данных из порта массив SerialMessageIn мы копируем в буфер, совмещенный адресом с указателем на переменную типа TD300_Trk_Point_Type. После чего можно выделить поля записи. Широту и долготу преобразуем в градусы, поделив на константу Convert.

Данную функцию мы вызовем после получения пакета данных от Навигатора. В результате на экране мы можем увидеть следующее сообщение. Время, для простоты примера, преобразовывать не будем. Видно, что данные прочитаны правильно. Теперь их можно разместить в буфере, базе данных и пр. Нажимая последовательно Ok можно видеть, как меняется число в поле Время. Для наглядности можно записать Треклог, стоя на одном месте, но с интервалом записи одна секунда. Что и представлено ниже.

Рис. 2
Формат D300

Рис. 3
Формат D301

Извините, закрасил я координаты своей форточки, а то обкурятся Саддамовские баллистики, да и залетит ко мне в окно…

Формат D300 самый простейший, расширением его служит формат D301. Он поддерживается аппаратами семейства GPSMAP 162/168, eMap, eTrex, GPSMAP 295. Начало его такое же, как и у D300.

TD301_Trk_Point_Type = record
posn : TSemicircle; // position
time : longword; // time
alt : single; // altitude in meters;
dpth : single; // depth in meters
new_trk : boolean; // new track segment?
end;

Если какой либо из параметров не поддерживается (не пересылается) то Навигатор вместо него дает число 1.0 е + 25. Например, Вентура не пересылает параметр «Глубина». Вместо него передается 10 в степени 25. Кстати, совершенно непонятно, что такое «глубина»? Знаете – расскажите…

Функция декодирования формата D301 немного изменяется, но несущественно. Так что я не буду приводить ее.

ПРОДОЛЖЕНИЕ И ДОРАБОТКА СЛЕДУЕТ… CYBER.

virpasha@rambler.ru

(к вирусам никакого отношения не имеет, -название конторы, где я работал)

13449020


GPS Pages | Home