Pl/Protokół ogólny

From FlightGear wiki
Jump to navigation Jump to search

FlightGear obsługuje wiele jednoczesnych połączeń I/O.

Można użyć protokołu FGNetFDM (--native) lub protokołu --generic (który pozwala zaprojektować niestandardowe pakiety z określonymi polami). Oba te protokoły mogą wysyłać dane jako pakiety UDP, które następnie mogą być odczytywane przez zewnętrzny program przy użyciu standardowej komunikacji poprzez gniazdo sieciowe.

Ten sam podstawowy mechanizm pozwoliłby również zewnętrznemu programowi na wysyłanie poleceń sterujących, więc jeśli pewnego dnia będziesz chciał przeprowadzić testowanie sprzętu lub oprogramowania w pętli kontrolera bezzałogowego statku powietrznego (UAV), jest to również możliwe.

Istnieje nieco skomplikowany, ale dość elastyczny mechanizm określenia kanału I/O, rodzaju komunikacji jaki będzie przez niego przesyłany, w jakim kierunku i z jaką szybkością. Poszukaj pliku o nazwie README.IO, który pokrótce wyjaśnia wiele z tych kwestii.

Ponadto istnieje ogólny protokół ("generic"), w którym można utworzyć plik XML określający dokładnie, jakie wartości danych mają być wysyłane przez kanał I/O. Nie mamy ogólnej obsługi "USB", ale jeśli masz urządzenie Serial -> USB, które wygląda jak port COM po stronie komputera, to powinno działać bez problemu... tylko uważaj, do którego portu COM zostanie przypisane urządzenie USB.

Istnieje protokół native_fdm do sterowania FlightGearem za pomocą zewnętrznego FDM oraz protokół ogólny ("generic"), który wykorzystuje pliki konfiguracyjne XML.

Ogólny ("generic") protokół komunikacyjny dla FlightGeara zapewnia potężny sposób dodawania prostego protokołu wejścia/wyjścia opartego na ASCII lub binarnego, poprzez zdefiniowanie pliku konfiguracyjnego zakodowanego w XML i umieszczenia go w katalogu $FG ROOT/Protocol/.

Protokół ogólny można łatwo skonfigurować zarówno dla wejścia, jak i wyjścia. Obsługuje on nie tylko jedno medium, ale może być łatwo skonfigurowany do korzystania z plików, kolejki FIFO, gniazd sieciowych itp. Protokół ogólny jest naprawdę dobry do takich rzeczy, jak eksportowanie do rozdzielonego pliku ASCII w celu zaimportowania go na przykład do arkusza kalkulacyjnego.

Myślę, że sensowne jest (w przypadku danych na żywo), aby ogólny protokół wchodził w pętlę while i odczytywał dane, dopóki nie będą już dostępne. W ten sposób, jeśli nadawca wysyła dane z większą prędkością lub FlightGear z jakiegokolwiek powodu zostaje w tyle, FlightGear nadrobi zaległości w każdej iteracji. Oczywiście nie działa to dobrze w przypadku zastosowania pliku jako wejścia, więc trzeba to rozróżnić.

Układ pliku XML

Plik XML protokołu może zawierać po jednym bloku <input> i <output> lub tylko jeden z nich. To, który z nich jest używany, zależy od sposobu wywołania protokołu. Poniższy przykład używałby tylko bloku definicji <output>.

--generic=file,out,1,/plik/wynikowy.xml,moj-protokol

Jeśli korzystasz z portu szeregowego w systemie Windows, musisz użyć specjalnej sekwencji ucieczki dla numerów portów COM wyższych niż COM9, np.:

--generic=\\.\COM10,out,1,/plik/wynikowy.xml,moj-protokol

Więcej szczegółów można znaleźć w artykule CreateFileA function (fileapi.h)

Przegląd pliku XML

 <?xml version="1.0"?>
 <PropertyList>
    <generic>
 
        <output>
            <binary_mode>false</binary_mode>
            <line_separator></line_separator>
            <var_separator></var_separator>
            <preamble></preamble>
            <postamble></postamble>
 
            <chunk>
                <!-- Definicja pierwszego fragmentu wyjściowego -->
            </chunk>
 
            <chunk>
                <!-- Następna definicja fragmentu wyjściowego itd. -->
            </chunk>
        </output>
 
        <input>
            <line_separator></line_separator>
            <var_separator></var_separator>
 
            <chunk>
                <!-- Definicja pierwszego fragmentu wejściowego -->
            </chunk>
 
            <chunk>
                <!-- Następna definicja fragmentu wejściowego itd. -->
            </chunk>
        </input>
 
    </generic>
 </PropertyList>

Parametry wejścia/wyjścia

Oba bloki <input> i <input> mogą zawierać informacje o type danych (tekstowy lub binary) oraz o separatorach między polami i zestawami danych, a także listę fragmentów <chunk>. Każdy <chunk> definiuje właściwość, która powinna zostać zapisana (i w jaki sposób) lub zmienną wraz z właściwością, do której należy ją zapisać.

Parametry protokołu ASCII

Należy pamiętać, że niektóre parametry mogą być używane tylko na wyjściu.

Znacznik parametru Out In Typ Domyślnie Przeznaczenie
<preamble> Yes No String "" Nagłówek umieszczony na górze pliku
<postamble> Yes No String "" Stopka umieszczona na końcu pliku
<binary_mode> Yes Yes Boolean False Wskazuje, czy ma być używany tryb binarny zamiast ASCII
<var_separator> Yes Yes String "" Separator pól
<line_separator> Yes Yes String "" Separator między zestawami danych
  • <var_separator> jest umieszczany pomiędzy każdą z właściwości.
  • <line_separator> jest umieszczany na końcu każdego zestawu danych.

Oba mogą zawierać dowolne ciągi znaków lub jedno z poniższych słów kluczowych:

Słowo kluczowe Znak specjalny
newline \n
tab \t
formfeed \f
carriagereturn \r
verticaltab \v

Typowe zastosowanie może być następujące:

 <var_separator>tab</var_separator>
 <line_separator>newline</var_separator>

lub

 <var_separator>\t</var_separator>
 <line_separator>\r\n</line_separator>

Parametry protokołu binarnego

Aby włączyć tryb binarny, wystarczy dołączyć znacznik <binary_mode>true</binary_mode> w pliku XML. Format wyjścia binarnego jest ściśle upakowany, z 1 bajtem dla bool, 4 bajtami dla int i 8 bajtami dla double. Obecnie ciągi znaków nie są obsługiwane. Konfigurowalna stopka na końcu każdej "linii" lub pakietu danych binarnych może zostać dodana za pomocą znacznika <binary_footer>. Opcje obejmują długość pakietu oraz magiczną liczbę upraszczającą dekodowanie. Przykłady:

  <binary_footer>magic,0x12345678</binary_footer>
  <binary_footer>length</binary_footer>
  <binary_footer>none</binary_footer>                 <!-- domyślnie -->

Ponadto można określić kolejność bajtów wysyłanych danych. Odbywa się to za pomocą flagi <byte_order>. Dostępne są dwie opcje: host (big endian) i network (little endian).

  <byte_order>host</byte_order>
  <byte_order>network</byte_order>        <!-- domyślnie -->

Jeśli plik protokołu ogólnego określa

    <binary_mode>true</binary_mode>
    <byte_order>network</byte_order>

to program odbierający (lub wysyłający) również musi używać tej konwencji kodowania. Kodowanie używane przez ogólny protokół jest niezależne od wybranego kanału wyjściowego (plik, gniazdo TCP, gniazdo UDP, port szeregowy lub cokolwiek innego).

Również w przypadku wysyłania/odbierania pakietów UDP należy używać różnych numerów portów dla kanałów wysyłania i odbierania.

Jeśli nie jesteś pewien, jakiego kodowania używa FG dla danych binarnych, plik src/Network/generic.cxx jest miejscem, w którym możesz się tego dowiedzieć. Z tego co wiem, jest ono dość standardowe dla podstawowych typów.

Kolejność sieciowa to najpierw MSB (ang. most significant bit, najbradziej znaczący bit) (podobnie jak kolejność hosta, jeśli system jest big endian, ale nie jest to obecnie tak powszechne). Jeśli używasz kolejności hosta i oba systemy mają tę samą kolejność bajtów (i zależy Ci tylko na twoim przypadku użycia), nie musisz się tym martwić.

Jeśli nie określisz kolejności bajtów, zostanie użyta kolejność hosta. Jakikolwiek x86 najprawdopodobniej użyje kolejności LSB (ang. least significant bit, najmniej znaczący bit) (komputer PC z pewnością).

Parametry zmiennej - specyfikacja <chunk>

Zarówno blok <input>, jak i <output> mogą zawierać listę specyfikacji <chunk>, z których każda opisuje właściwości zmiennej do zapisu/odczytu.

<name>

Dla ułatwienia użytkowania i nieprzenoszenia (jak znacznik notatek)

<node>

Węzeł drzewa właściwości, który dostarcza dane

<type>

Typ wartości wymagany do formatowania, jako jeden z: string, float, bool lub int (domyślnie: int). Zaleca się, aby ten znacznik był obecny, w przeciwnym razie mogą wystąpić fałszywe wyniki.

<format>

Tylko dla protokołu tekstowego, nie jest używany ani potrzebny w trybie binarnym. Definiuje rzeczywisty fragment tekstu, który powinien zostać wysłany. Może zawierać opcje formatowania w stylu "printf", takie jak:

Opcja formatowania Typ
%s String
%d Integer (domyślnie)
%f Float

Uwaga: atrybut type mówi drzewu właściwości, jak przechowywać wartość wewnętrznie, używając najbardziej odpowiedniego typu danych oszczędza pamięć i zmniejsza narzut konwersji (bool, int, float, double, string). Używanie ciągów formatu w stylu printf w pliku XML protokołu ogólnego, jedynie informuje system wejścia/wyjścia, jak kodować/dekodować właściwość - tak, aby można ją było poprawnie zapisać/przesłać i przywrócić. Użycie niewłaściwego typu i/lub ciągu formatu w protokole binarnym spowodowałoby przesłanie/przetworzenie niewłaściwych danych przez FG.

Zasadniczo każdy specyfikator typu mówi FG, jak dużo pamięci zajmuje dana (w bitach). Na przykład wartość logiczna bool to tylko 0 lub 1 - więc wymaga tylko jednego bitu. Z drugiej strony a liczba całkowita (int) 4 bajtów (32 bit), a 1 bajt to 8 bitów.

Tak więc powiedzenie FG, że coś jest bitem, spowoduje obcięcie danych do samego bitu - o to właśnie chodzi w specyfikatorach formatu: możesz wpływać na reinterpretację wartości z drzewa właściwości. Ponadto istnieje koncepcja znkowania, tj. liczby dodatnie lub ujemne lub liczby posiadające mantysę - wpływają one na sposób przechowywania wartości w pamięci, a zatem program odbierający musi wiedzieć, w jaki sposób wartość została zakodowana, aby przekształcić ją z powrotem w to, co chcesz.

<factor>

Opcjonalny mnożnik, który może być używany do konwersji jednostek, na przykład radianów na stopnie.

<offset>

Opcjonalne przesunięcie, które może być używane do konwersji jednostek, na przykład stopni Celsjusza na stopnie Fahrenheita.

<format>

Fragmenty mogą również składać się z pojedynczej stałej <format>, jak w:

 <format>Data Section</format>

Szeregowe I/O

Możesz przypisać dowolny numer do portu COM, gdy jest on sterowany przez adapter USB-COM lub Ethernet-COM. Wystarczy przejść do Menedżera urządzeń i ustawić go w zakresie od 1 do 255.

Istnieje wiele marek tego rodzaju urządzeń. Niektóre są dobre, inne nie bardzo.

Uwaga  Windows: aby określić numer portu COM większy niż 9, należy użyć następującej składni: \\.\COM10. Ta składnia działa dla wszystkich numerów portów i sprzętu, który pozwala na określenie numerów portów COM.

Nie wygląda na to, że kod interfejsu protokołu ogólnego jest skonfigurowany do nadawania i odbierania w tym samym czasie. Zatem nie można otworzyć tego samego urządzenia dwa razy, więc dwie opcje wiersza poleceń również nie będą działać. O ile wiem, będzie to wymagało pewnych modyfikacji kodu, jeśli chcesz korzystać z bezpośredniej komunikacji szeregowej.

Inną opcją może być napisanie małego kodu pośredniczącego, który komunikuje się z FlightGearem przez sieć i komunikuje się ze sprzętem przez port szeregowy, a następnie wykonuje odpowiednią translację danych zgodnie z wymaganiami.

Dobrym sposobem na sprawdzenie, czy transmisja wartości float działa, jest wydrukowanie wartości w programie odbierającym i porównanie jej z wartością właściwości przesyłanej przez FlightGear. Przy okazji, jeśli Twój protokół ogólny jest ustawiony na używanie networkowej kolejności bajtów (MSB), musisz wziąć to pod uwagę podczas rozpakowywania wartości float.

Przykłady

Przykład 1

Zapisanie logów tego formularza:

V=16
H=3.590505
P=3.59
V=12
H=3.589020
P=3.59
 <?xml version="1.0"?> 
 <PropertyList>
 <generic>
 
    <output>
      <line_separator>newline</line_separator>
      <var_separator>newline</var_separator>
      <binary_mode>false</binary_mode>
 
      <chunk>
        <name>speed</name>
        <format>V=%d</format>
        <node>/velocities/airspeed-kt</node>
      </chunk>
 
      <chunk>
        <name>heading (rad)</name>
        <format>H=%.6f</format>
        <type>float</type>
        <node>/orientation/heading-deg</node>
        <factor>0.0174532925199433</factor>  <!-- stopnie na radiany -->
      </chunk>
 
      <chunk>
        <name>pitch angle (deg)</name>
        <format>P=%03.2f</format>
        <node>/orientation/pitch-deg</node>
      </chunk>
   </output>
 
 </generic>
 </PropertyList>

Zapisywanie danych w formacie XML

Zakładając, że plik protokołu nazywa się $FG ROOT/Protocol/xmltest.xml, można go użyć w następujący sposób:

fgfs --generic=file,out,1,/tmp/data.xml,xmltest
 <?xml version="1.0"?>
 <PropertyList>
  <generic>
    <output>
      <binary_mode>false</binary_mode>
      <var_separator>\n</var_separator>
      <line_separator>\n</line_separator>
      <preamble>&lt;?xml version="1.0"?&gt;\n\n&lt;data&gt;\n</preamble>
      <postamble>&lt;/data&gt;\n</postamble>
 
      <chunk>
        <format>\t&lt;set&gt;</format>
      </chunk>
 
      <chunk>
        <node>/position/altitude-ft</node>
        <type>float</type>
        <format>\t\t&lt;altitude-ft&gt;%.8f&lt;/altitude-ft&gt;</format>
      </chunk>

Więcej przykładów można znaleźć w wątku listy mailingowej.

      <chunk>
        <node>/velocities/airspeed-kt</node>
        <type>float</type>
        <format>\t\t&lt;airspeed-kt&gt;%.8f&lt;/airspeed-kt&gt;</format>
      </chunk>
 
      <chunk>
        <format>\t&lt;/set&gt;</format>
      </chunk>
    </output>
 </generic>
 </PropertyList>

Analiza wynikowego formatu pakietu binarnego

W lokalizacji FlightGear/utils/xmlgrep można znaleźć narzędzie o nazwie "generic-protocol-analyse", które można użyć do analizy wynikowego pakietu danych dla protokołu binarnego. Dane wyjściowe będą wyglądać mniej więcej tak:

bintest.xml
Generic binary output protocol packet description:

 pos | size |  type  | factor     | description
-----|------|--------|------------|------------------------
   0 |    4 |    int |            | indicated speed (kt)
   4 |    4 |  float |            | pitch att (deg)
   8 |    4 |  float |            | magnetic heading (deg)
  12 |    4 |    int |            | outside air temperarure (degF)
  16 |    1 |   bool |            | autocoord

total package size: 17 bytes


Protokół ogólny

FlightGear ma wbudowany "ogólny" protokół do przesyłania i pobierania danych. (todo)

Aby na przykład połączyć się z FlightGear na gnieździe, należy wykonać następujące kroki:

  1. Ustalenie, które właściwości mają być wysyłane.
  2. Utwórz plik my_protocol.xml zawierający te właściwości.
  3. Uruchom FlightGear z opcją protokołu przy użyciu my_protocol wysyłanym przez gniazdo sieciowe.

Dla tego prostego przykładu powiedzmy, że jesteśmy zainteresowani jakimi wartościami jak kurs i wysokość i chcemy wysyłać dane do gniazda na porcie 6789, udp i odbierać je 10 razy na sekundę w formacie:

alt\thead\n 
// ie altitude - tab - heading - newline

Pierwszym krokiem jest ustalenie właściwości, w tym przykładzie będą to:

/position/altitude-agl-ft  eg (22.4713923)
/orientation/heading-deg  eg (297.966325)

Następnie utwórz plik my_protocol.xml. Musi on znajdować się w katalogu:

$FG_ROOT/Protocol/my_protocol.xml

Edytuj plik my_protocol.xml i zapisz w nim:

 <?xml version="1.0"?>
 <PropertyList>
 <generic>
    <output>
        <line_separator>newline</line_separator>
        <var_separator>tab</var_separator>
 
        <chunk>
           <node>/position/altitude-agl-ft</node>
           <name>altitude to go</name>
           <type>float</type>
           <format>%03.2f</format>
         </chunk>
 
        <chunk>
           <node>/orientation/heading-deg</node>
           <name>Heading</name>
           <type>float</type>
           <format>%03.3f</format>
         </chunk>
 
    </output>
  </generic>
 </PropertyList>
  • Znacznik <output> wskazuje protokół wyjściowy. Ten sam plik XML może zawierać takę sekcję <input>, nieobecną w tym przykładzie (więcej później).
  • <line_seperator> - znacznik końca linii (TODO: czy to jest \r\n?)
  • <var_seperator> - separator dla właściwości, może to być "|", "###", dowolny ciąg znaków.
  • Dwa bloki <chunk> zawierają nasze dane. Należy pamiętać, że są one wyświetlane w kolejności przedstawionej w pliku XML.
    • <node> - właściwość danej którą potrzebujemy.
    • <name> - nazwa nie jest przesyłana i jest traktowana jak notatka, pomocna dla nas
    • <type> - sposób, w jaki protokół ma interpretować wartość. Jest to dość ważny znacznik. Na przykład, jeśli go nie ma lub jest ciągiem znaków, to liczby float szaleją i kończą jako ciąg 32 znaków z dwoma miejscami po przecinku!
    • <format> - taka sama składnia jak dla funkcji printf w C/C++.
    • Jest tego więcej {TODO - link do pełnego przykładu}

Możemy teraz uruchomić FlightGeara z następującą opcją wiersza poleceń:

fgfs --generic=socket,out,10,localhost,6789,udp,my_protocol

[TODO] - link tutaj do samouczka

Alternatywne uruchomienie poprzez Nasal

Możliwe jest również uruchamianie i zatrzymywanie protokołu ogólnego z poziomu konsoli Nasal. Uruchom konsolę Nasal z menu Debug i wpisz następujące polecenie (odpowiednio zmodyfikuj) i kliknij przycisk Execute:

fgcommand("add-io-channel", {"config": "generic,socket,out,10,localhost,6789,udp,my_protocol", "name": "test"});

Nazwa jest opcjonalna, ale może być użyta do zatrzymania połączenia:

fgcommand("remove-io-channel", {"name": "test"});

Treści powiązane

Artykuły Wiki

Pliki Readme

Kod źródłowy