Програма для читання даних із GPS-навігатора
Тепер створимо для прикладу практичну програму для спілкування комп'ютера з 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.334 $gpbod,,т,,м,,*47
$ hchdg,147.3. 9 5,е*24
Кожне повідомлення починається з символу $ і закінчується перекладом рядка (+, тобто символами з номерами 13 і 10), окремі поля розділяються комами. Останній байт перед цими символами (у першому рядку)— контрольна сума. Ми її відслідковувати не будемо. Якщо навігатор якусь величину "не знає", то відповідне поле між комами буде порожнім. Цікаво, що між однаковими полями повідомлень є деякі різночитання, які, очевидно, викликані тим, що повідомлення формуються в різний час, хоч і видаються одночасно.
Все це море інформації нам не потрібно, давайте обмежимося простим завданням: вивести в віконця координати місця (у нормальних одиницях широти/довготи), висоту місця над рівнем моря та точний час за Гринвічем. Для цього достатньо проаналізувати, наприклад, повідомлення 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 байти).
Forml: TForml; st,stcorn:string;
FlagCOM:boolean=False; FlagGPS:boo lean-False; call:integer;
Л процедура ініціалізації порту виглядатиме так:
процесу IniCOM; var i, err :integer; begin
Fo rml.Memo1.Li nes.Add(s t);
Forml.AfComPortl.Close;(закриваємо старийСОМ, якщобув/
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 doapplication.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);
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
prooedure TForml.FormDestroy(Sender: TObject); begin
AfComPort1.Close;Iзакриття порту>end;
prooedure TForml.ComboBoxlSelect(Sender: TObject); begin
prooedure TForml.MemolChange(Sender: TObject); begin
Як бачите, для краси ми додали процедуру приховування текстового курсору. І нарешті, найголовніша процедура прийому даних, аналізу рядків повідомлення та виведення результатів на екран:
prooedure TForml.AfComPortlDataRecived(Sender: TObject; Count: Integer); var stl:string; sek:real; i,err:integer; begin
tall:=0; /пристрій виявленоI
if pos('$',st.)=0 then begin st: = ”; exit end;
if length(st) 0 thenbegin(аналіз повідомлення)Memol.Lines.Clear;(st:='$GPGGA,154138,5552.6224,N,03733.3334,E.1,04, 3. 5,268. 5, M, 15. 4, M,, 45′;)