Главная » Delphi » Программа для чтения данных с GPS-навигатора

0

Теперь создадим для примера вполне практическую программу для общения компьютера с GPS-навигатором по протоколу NMEA. В качестве прототипа используем процедуру с использованием компонента AsyncFree, но читатель, при нежелании устанавливать этот компонент, без сомнения, легко перепишет ее с использованием функций API. Тем более что общение с устройством тут полностью одностороннее.

Сначала о протоколе NMEA. Хотя эта программа испытывалась с навигатором Garmin eTrex, никакой разницы нет — любой навигатор, от самых сложных профессиональных приборов до дешевых ручных устройств поддерживает этот протокол. Для этого его нужно установить в меню установок прибора — по умолчанию гам, как правило, установлен фирменный протокол (для фирмы Garmin это и есть упоминавшийся ранее протокол Garmin). Хотя фирменный протокол имеет более широкие возможности (см. [27]), но NMEA обычно достаточно, чтобы извлечь из навигатора практически всю информацию, которую он умеет выдавать. Протокол этот был разработан Национальной ассоциацией морской электроники (National Marine Electronics Association, NMEA) еще до появления GPS н под названием NMEA0183 (IECI162) стал в настоящее время мировым стандартом. Крупнейшим недостатком протокола обычно называют невозможность присоединения более чем одного устройства к одному порту.

Для общения с навигаторами есть» как уже упоминалось, специальный компонент ActiveX, но мы обойдемся без него. Технические характеристики NMEA по умолчанию всегда такие: скорость 4800, формула передачи 8п1. передача производится ежесекундно. Частичное описание протокола на русском есть, например, здесь [28]. Полностью его можно найти на сайте самой NMEA за большие деньги, но и частичного описания для подавляющего большинства нужд более чем достаточно. Вот что каждую секунду выдает у меня навигатор Garmin eTrex:

$gprmc,154140,а,5552.6225,n,03733.3341,е,0.0, 0.0, 080405, 9.5,е,а*1е 3gprmb,а,,,,,,,,,,,,а,а*0в

5gpgga,154138,5552.6224,n,03733.3334,е,1,04,3.5,268.5, м, 15.4, м, ,’45 5gpgsa,а,2,,,04,13,,,23,24,,,,,3.7,3.5,1.0*31

3gpgsv,3,2,10,16,13,111,00,20,69,147,00,23,69,247,43,24,49,246,47*72 5gpgll,5552.6224,n,03733.3334,е,154138,а,а*4в $gpbod,,т,,м,,*47

$gpvtg,0.0,t,350.5,m,0.0,n,0.0,k*4d

$pgrme,42.2,m,49.1,м,64.8,м*1с

spgrmz,489,?*01

$pgrmm,wgs 84*06

$hchdg,147.3,,, 9 5,е*24

$gprte,1,1,с,*37

Каждое сообщение начинается с символа $ и заканчивается переводом строки (<CR>+<LF>, т. е. символами с номерами 13 и 10), отдельные поля разделяются запятыми. Последний байт перед этими символами (ie в первой строке)— контрольная сумма. Мы ее отслеживать не будем. Если навигатор ка- кую-то величину "не знает", то соответствующее поле между запятыми будет пустым. Забавно, что между одинаковыми полями сообщений имеются некоторые разночтения, которые, очевидно, вызваны тем, что сообщения формируются в разное время, хотя и выдаются единовременно.

Все это море информации нам не требуется, давайте ограничимся простой задачей: вывести в окошки координаты места (в нормальных единицах широты/долготы), высоту места над уровнем моря и точное время по Гринвичу. Для этого достаточно проанализировать, например, сообщение sgpgga.

Первое поле в этом сообщении — время по Гринвичу (Greenwich Mean Time, GMT), второе и третье— широта (n — значит "северная", North), четвертое и пятое— долгота (е— значит "восточная", East). Высота места над уровнем моря— поле перед первым по ходу строки символом м, который обозначает единицу ее измерения (метр, Meter). Надо так понимать, что могут быть и футы, но это явно должно устанавливаться в самом навигаторе, так что будем рассчитывать на метры, в крайнем случае программа просто не выведет ничего. Разберемся с представлением координат— что это за числа: 5552.6225, 03733.3341? Оказывается, это десятичные градусы, точнее— минуты. Первые два (или три для долготы) знака есть целые градусы, еще два до точки — минуты, остальное есть секунды, выраженные в десятичных долях вместо шестидесятеричных. Для того чтобы перевести их в привычные секунды, достаточно дробную часть умножить на 60 — получим секунды с сотыми долями.

Заметки на полях

Кстати, любопытно, а какова получается разрешающая способность такого представления координат (четыре разряда минут после запятой), если ее выразить в метрах? По широте на этот вопрос можно ответить более-менее однозначно: 1 минута широты (угловая минута дуги меридиана) есть одна морская миля, т. е. 1852 метра (отсюда 1 градус — примерно 111 км). Тогда одна десятитысячная часть составит примерно 20 см (а одна секунда широты — 31 м, а ее сотая часть — 31 см, как видите, при переводе долей десятичных минут в углоаые секунды мы теряем в разрешении). На самом деле это цифры приблизительные, потому что при таких точностях надо учитывать форму геоида, навигаторы делать это умеют, и в сообщениях NMEA информация для этого содержится, но углубляться в этот вопрос здесь мы не будем. Заметим только, что для долготы уже такой однозначности нет — к полюсам меридианы сходятся, на широте 60 градусов длина широтной дуги становится в два раза меньше меридиональной. Для прикидок а уме можно принимать, что для средних широт 40—50° длина 1 минуты по долготе примерно в 1,5 раза меньше, чем по широте. А методика более точных расчетов по данным GPS-навигатора имеется на множестве ресурсов в Сети— дисциплины геодезия и картография, ранее бывшие прерогатиаой отдельных специалистов с военным и геологическим уклоном, теперь пришли в широкие массы, которые раньше даже и не подозревали о существовании какого-нибудь эллипсоида Красовского.

Разобрались, теперь можно писать программу. Для этого создадим новый проект под названием GPSNavigate (в папке GIava21\4), установим на форму то же самое Memo с теми же свойствами, как и в проекте COMProba ранее (только уберем у него линейку прокрутки: scrollBars = ssNone), и тот же самый AfComPort. Из визуальных компонентов нам потребуется еще только сотЬовох для выбора порта, который мы тоже скопируем из проекта-пробы. При инициализации порта стоит учесть, что в каждой посылке навигатора может быть порядка 500 символов, и на всякий случай установить буфер побольше (пусть это будет 1024 байта).

В секции объявления переменных мы запишем следующее:

var

Forml: TForml; st,stcorn:string;

FlagCOM:boolean=False; FlagGPS:boo lean-False; call:integer;

Л процедура инициализации порта будет выглядеть так:

procedure IniCOM; var i, err :integer; begin

FlagCOM:=False; st:=’COM?';

Forml.Memo!.Lines.Clear;

Fo rml.Memo1.Li nes.Add(s t);

{инициализация COM – номер в строке sccom/

Forml.AfComPortl.Close; (закрываем старый СОМ, если был/

val(stcom[length(stcom)],i,err);

if err=0 then Forml.AfComPort1.ComNumber:=i else exit;

(номер порта/ Forml.AfComPortl.BaudRate:=br4800; (скорость 4800/ Forml.AfComPortl.InBufSize:=1024; (емкость буфера) try

Fo ml. A f ComPo r 11. Open ; except

if not Forml.AfComPortl.Active then (если не открылся/ begin

st:=stcoirv+’ does not be present or occupied.'; Application.MessageBox(Pchar(st),’Error’,MB_OK); exit (выход из процедуры – неудача) end; end;

st: = ‘AT’ i’iil3+#10; (будем посылать инициализацию модема/ Forml.AfComPortl.WriteString(st); (ответ не сразу) Forml.Timerl.Enabled:-True; tall:=0;

while callcl do application.ProcessMessages; Iпауза в 1 с) Forml.Timerl. Enabled: ^ False;

st:=Forml.AfComPortl.ReadString; (ответ модема 10 знаков) FlagGPS:=False; (это еще не проверка GPS) if pos(‘OK’,st)<>0 then (модем/ begin

st:=stcomi’ занят модемом'; Fo rml.Timerl.Enabled: Fa lse; Forml.Memol.Lines.Clear; Forml.Memol.Lines.Add(st); exit;

end else (все нормально, открываем СС&!) begin

Forml.Memol.Lines.Clear; s t: =’ ‘; (очюцаем строку для вывода} FlagCOM:=True; tall:=0;

Forml.Timerl.Enabled:^True; (запускаем опять таймер) FlagGPS:=True; (проверка GPS) end; end;

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

Процедура по событию таймера будет выглядеть так:

procedure TForml.TimerlTimer(Sender: TObject);

begin

inc(tall);

if (tall>2) and (FlagGFS=True) then (устройство не обнаружено) begin

st: =’Устройство не обнаружено1; Forml.Memol.Lines.Clear; Forml.Memol.Lines.Add(st) ; end; end;

To есть таймер работает все время (отсчитывая секунды в переменной tail, т. к. величину свойства interval мы не меняли, она по умолчанию I с и равна). Для того чтобы теперь отслеживать контакт с устройством, достаточно каждый раз обнулять переменную tall в процедуре приема— как только промежуток между поступлениями данных превысит 2 с, программа "завопит", что "устройство не обнаружено" и автоматически возобновит прием, как только данные пойдуг опять.

Прежде чем перейти к процедуре приема, создадим все остальные необходимые процедуры, они гут будут очень просты:

procedure TForml.FormCreate(Sender: TObject); begin

stcom:=’COMl';

IniCOM;

end;

prooedure TForml.FormDestroy(Sender: TObject); begin

AfComPort1.Close; Iзакрытие порта} end;

prooedure TForml.ComboBoxlSelect(Sender: TObject); begin

Forml.Timerl.Enabled:=False; {останавливаем таймерI stcom:=ComboBoxl.Text; {устанавливаем порт СОМ1,2,3,4) IniCOM; end;

prooedure TForml.MemolChange(Sender: TObject); begin

HideCaret(Forml.Memol.Handle); {скрываем курсор) end;

Как видите, для красоты мы добавили процедуру скрытия текстового курсора. И наконец, самая главная процедура приема данных, анализа строк сообщения и вывода результатов на экран:

prooedure TForml.AfComPortlDataRecived(Sender: TObject; Count: Integer); var stl:string; sek:real; i,err:integer; begin

if FlagCOM=False then exit; {если модем еще не опрошен)

tall:=0; /устройство обнаруженоI

st:=st+Forml.AfComPortl.ReadString; {посылка CPS I

if pos(‘$’,st.)=0 then begin st: = ”; exit end;

{если знак $ имеется)

delete(st,1,pos(‘§st)-1); {до знака $ все удаляем)

if length(st)<6 then exit; {накапливаем I

if pos(‘GPGGA’,st)=0 then begin st:=”; exit end;

{не то сообщение) if pos(chr(13)+chr(10),st)<>0 then {конец сообщения GPGCA) begin (анализ сообщения) Memol.Lines.Clear; (st:=’$GPGGA,154138,5552.6224,N,03733.3334,E.1,04, 3. 5,268. 5, M, 15. 4, M,, 45′;)

stl:=copy(st,pos(1,’,st)+1,6);(время) if pos(‘,’,stl)<>0 then (нет данных) begin stl:=’HeT данных';

Memol.Lines.Add(stl) ; st:=”; expend;

insert(‘:’, stl, 5);

insert(‘: ‘,stl,3);

Memol.Lines.Add(‘Время GMT: ‘+stl);

delete (st, l,pos (‘, 1, st));

delete (st, l,pos (‘, 1, st)); {широта)

stl:=copy(st,pos (1.’ ,st), 4); {две. секунмьг)

val(stl,sek,err);

sek:=sek*60; {обычные секунды)

str(sek:5:2,stl);

stl :=stl+’"" ;

stl:=copy(st,l,pos(‘.’,st)-l)+stl; {широта градусы + минуты)

insert(”*, stl,5);

insert(‘°1,stl,3);

delete (st, l,pos (‘, st)); {какая?)

stl:=stl+’ ‘+copy(st,1,1); {N)

Memol.Lines.Add(‘Широта: ‘+stl);

delete(st,l,pos(‘,’,st)); {долгота)

stl:=copy(st,pos(‘.’,st),4); {дес. секунды)

val(stl,sek,err);

sek:=sek*60; {обычные секунды)

str(sek:5:2,stl);

stl:=stl+"";

stl:=copy(st,1,pos(‘.’,st)-1)+stl; {долгота градусы + минуты)

insert(‘*stl,6);

insert(‘°1,stl,4);

delete(st,l,pos(‘,1,st)); {какая?)

stl: =stl+’ ‘+copy (st, 1,1); {E)

Memol.Lines.Add(‘Долгота: ‘+stl);

{теперь найдем высоту над уровнем моря:)

stl:=”;

i:=pos(‘M’,st)-2;

while st[i]<>’,’ do begin stl:=st[i]+stl;i:=i-l; end; Memol.Lines.Add(‘Высота над уровнем моря: ‘+stl+’ м’); st:="; end; end;

Закомментированная строка с образцом сообщения $gpgga предназначена для того, чтобы было удобнее отлаживать программу. В остальном я подробно описывать процедуру не буду — там все понятно из комментариев в тексте.

Результаты работы программы с "живым" GPS-навигатором показаны на рис. 20.3. Кстати, если вы захотите направить по этим координатам ракет\ класса "земля-земля", то имейте в виду, что ошибка определения коордииа! моей форточки составляла в этом сеансе не меньше 140 метров, а из стратегически важных объектов в этом радиусе имеется только частная "Автомойка" и небольшой ларек с чипсами и пивом.

Рис. 20.3. Результат работы программы GPSNavigate

Источник: Ревнч Ю. В.  Нестандартные приемы программирования на Delphi. — СПб.: БХВ-Петербург, 2005. — 560 е.: ил.

По теме:

  • Комментарии