Komunikacja z programami trzecimi

Z MaSzyna
Wersja z dnia 10:00, 7 kwi 2018 autorstwa Firleju (dyskusja | edycje) (Parametry ramek)
(różn.) ← poprzednia wersja | przejdź do aktualnej wersji (różn.) | następna wersja → (różn.)
Skocz do: nawigacja, szukaj

Począwszy od wersji 1.3 została wprowadzona możliwość komunikacji z innym programem w oparciu o mechanizm WM_COPYDATA. Umożliwia ona śledzenie stanu symulacji jak również modyfikowanie przez inny program. Obsługa mechanizmu została wprowadzona w Rainsted 1.0.94, ale również inne programy mogą się komunikować z Symulatorem.

Ponieważ niektóre programy potrafią śmiecić w systemie komunikatami WM_COPYDATA wysyłanymi do wszelkich okien, komunikacja jest dodatkowo zabezpieczona dwiema sygnaturami o rozmiarze 32 bitów. Użycie sygnatur zmniejsza prawdopodobieństwo przetworzenia śmieciowej ramki do 1:1.8×1019. Jedna sygnatura jest w ramce WM_COPYDATA, a dodatkowa w obszarze danych.

Włączenie komunikacji

Aby komunikacja była aktywna, należy w pliku EU07.INI umieścić wpis

multiplayer 1

Opcja ta ma wpływ również na symulację, między innymi wyłącza możliwość zatrzymania symulacji klawiszem [Pause]. Od wersji 439 powinno działać również

multiplayer 2

w którym możliwe jest pauzowanie wraz z powiadomieniem programu nadzorującego (zarządzającego ruchem na scenerii).

Ramka WM_COPYDATA

Komunikujące się okna (w sensie lpClassName) powinny się nazywać EU07 (starsze wersje Symulatora) oraz TEU07SRK (inny program; litera "T" ułatwia pisanie programów w kompilatorach firmy Borland – wystarczy utworzoną formę nazwać "EU07SRK"). Struktura ramki jest określona przez system operacyjny i nazwana COPYDATASTRUCT. Nie potrzeba jej definiować samodzielnie.

UWAGA! W nowszych wersjach Symulatora, okno ma nazwę GLFW30 i tylko z nią inne programy są w stanie przesyłać informacje do Symulatora.

typedef struct tagCOPYDATASTRUCT {
  ULONG_PTR dwData; //sygnatura 'EU07'
  DWORD     cbData; //długość bufora danych
  PVOID     lpData; //wskaźnik do bufora danych
} COPYDATASTRUCT, *PCOPYDATASTRUCT;

Komunikacja jest zabezpieczona za pomocą sygnatury umieszczonej w polu dwData. Dopuszczalny jest jedynie ciąg znaków 'EU07', czyli szesnastkowo wartość 0x37305545. Ramki o innej sygnaturze będą ignorowane. W przyszłości możliwe jest także rozpoznawanie innych sygnatur.

Pole cbData określa długość ramki danych, a pole lpData jest wskaźnikiem na ramkę danych.

Długość podana w cbData może być większa, niż faktyczna ilość użytecznych danych, umieszczonych w ramce. Nie ma to większego znaczenia, gdyż pomiędzy programami dane nie są kopiowane, przekazywany jest tylko wskaźnik do obszaru danych.

Ramka danych

Używane obecnie ramki danych nie przekraczają długości 256 bajtów, ale teoretycznie możliwe jest także użycie dłuższych. Długość ramki danych jest określona w ramce WM_COPYDATA. Ramka o wielkości do 256 bajtów ma następującą strukturę:

struct DaneRozkaz
{//struktura komunikacji z EU07.EXE
 int iSygn; //sygnatura 'EU07'
 int iComm; //rozkaz/status (kod ramki)
 union
 {float fPar[62]; //wartości zmiennoprzecinkowe
  int iPar[62]; //wartości całkowite
  char cString[248]; //upakowane napisy
 };
};

Pole iSygn zawiera sygnaturę (jak wyżej), która zabezpiecza przed rozpoznaniem śmieciowych ramek. Również w tym przypadku możliwe jest, że w przyszłości dopuszczone będą inne sygnatury.

Pole iComm określa znaczenie dalszych wartości. W zależności od kierunku przesyłania jest albo kodem rozkazu (operacja do wykonania), albo statusem (potwierdzeniem stanu).

Dalsza część ramki może zawierać czterobajtowe wartości zmiennoprzecinkowe, czterobajtowe wartości całkowite lub ciągi znaków (napisy). Wartości liczbowe są zawsze umieszczone przed napisami, a napisy stanowią ostatnią część ramki. Napis zwykle składa się z jednobajtowego licznika (nie obejmującego ani licznika ani znacznika końca), ciągu znaków różnych od zera oraz znacznika końca (bajt 0x00). Dalej może być kolejny napis o takiej samej strukturze. Ilość napisów jest zależna od numeru ramki. Jeśli np. ramka przewiduje dwa napisy, a drugi z nich jest pusty, to jest on reprezentowany dwoma zerowymi bajtami: licznik ma wartość 0x00, a po nim jest znacznik końca 0x00.

Kody ramek danych

Zawartość ramki danych jest przetwarzana zależnie od jej kodu (pole iComm). Nieznane kody są ignorowane. Ramki przyjmowane przez Symulator są na ogół operacjami do wykonania, po wykonaniu na ogół odsyłane jest potwierdzenie o tym samym kodzie.

Kod 0 - wersja programu

Ramka służy do przesłania wersji oprogramowania. Symulator odsyła napis identyczny z tym umieszczonym w pliku log.txt oraz wyświetlanym po naciśnięciu klawisza [F9]. Przykładowa treść:

Compilation 2011-09-06, release 1.3.253.231.

Obecnie nie jest to używane. Będzie miało ewentualnie znaczenie, gdy powstanie więcej komunikujących się programów. Przesłany numer wersji może służyć do włączenia/wyłączenia dodatkowych opcji lub być podstawą do domagania się aktualizacji oprogramowania do nowszej wersji w przypadku braku zgodności.

Kod 1 - nazwa scenerii

Po odebraniu ramki z kodem 1 Symulator odsyła nazwę pliku, z którego wczytano scenerię. Aktualnie nie jest to używane na poziomie komunikacji z Symulatorem.

Kod 2 - wykonanie eventu

Wykonanie eventu. Odebrana przez Symulator spowoduje dodanie do kolejki eventu o podanej nazwie, o ile takowy został zdefiniowany we wczytanej scenerii. Wykonanie eventu spowoduje odesłanie ramki potwierdzającej. Również wewnętrznie wyzwolenie wykonania wszelkich eventów multiple, switch, putvalues i getvalues powoduje wysłanie ramki potwierdzającej.

Ramka wysyłana do Symulatora może zawierać jeden napis, który jest nazwą eventu. Ramka potwierdzająca może mieć dodatkowo drugi napis, który jest nazwą pojazdu, który dany event wygenerował (np. odczyt semafora). Pozwala to śledzić położenie pojazdów na scenerii, jeśli wiadomo który tor może generować event o danej nazwie (na ogół tor powiązany z semaforem).

Może się zdarzyć, że przesłany event nie zostanie wykonany, albo nie zostanie potwierdzony. W takim przypadku wskazane jest powtórzenie ramki po upływie pewnego czasu (zagęszczenie kolejki eventów może spowodować opóźnienie potwierdzenia o kilka sekund).

Kod 3 - rozkaz dla AI

Ramka służy do przekazania rozkazu do pojazdu sterowanego przez AI.

Przesyłane są dwie wartości zmiennoprzecinkowe (fPar[0] i fPar[1]) oraz dwa napisy. Pierwszy napis jest nazwą rozkazu (np. Shunt), a drugi nazwą pojazdu.

Wykonanie nie jest obecnie potwierdzane, ponieważ funkcja ta jest używana wyłącznie do testów. Może się to zmienić w przyszłości, gdy pojazdy sterowane przez AI będą musiały być synchronizowane u wielu użytkowników podłączonych do serwera.

Kod 4 - test zajętości toru

Ramka służy do sprawdzania, czy tor jest wolny. Aby z tego korzystać, tory w scenerii muszą mieć unikalne nazwy. Ramka zawiera tylko jeden napis, który jest nazwą toru. Jeśli tor jest wolny, odsyłane jest potwierdzenie o identycznej treści. Jeśli na torze znajdują się jakiekolwiek pojazdy, potwierdzenie nie jest wysyłane.

Aby sprawdzić, czy tor został zwolniony, należy wysyłać zapytania cyklicznie.

Funkcja działa jedynie dla torów, nie dotyczy odcinków izolowanych. Również to może ulec zmianie w przyszłości.

Kod 5 - ustawienie czasu itp.

Ramka służy do synchronizowania czasu symulacji u użytkowników, w przyszłości może zostać rozbudowana o ustawianie dodatkowych parametrów o zbliżonym sensie.

Jeśli ustawiony jest bit 0 w polu iPar[0], to znajdująca się za nim wartość zmiennoprzecinkowa fPar[1] ustawia czas. Powinna to być wartość z przedziału <0,1), co odpowiada pełnej dobie (np. 0.5 - godzina 12:00). Ustawienie czasu powinno być wykonane na początku symulacji i raczej nie powinno być przestawiane w jej trakcie. Jeśli czas nie ma być przestawiony, to ta konkretna wartość zmiennoprzecinkowa nie ma znaczenia. Potwierdzenie nie jest wysyłane.

Od wersji 439, jeśli ustawiony jest bit 1 w polu iPar[0], to wartość całkowita iPar[2] zostanie przyjęta jako flagi zapauzowania. Pozwala to na zapauzowanie i ponowne uruchomienie symulacji z poziomu zewnętrznego programu.

Również od wersji 439 ramka ta jest wysyłana w momencie zmiany flag zapauzowania (np. awaria komunikacji z PoKeys powoduje włączenie pauzy).

Kod 6 - zapytanie o pojazd

Przyjmowana ramka zawiera tylko napis, który jest nazwą pojazdu. Odsyłana jest ramka z kodem 7.

Kod 7 - parametry ruchu pojazdu

Ramka zawiera zestaw wartości dotyczących pojazdu. W polu iPar[0] jest liczba tych wartości (aktualnie 9). Kolejne wartości zawierają:

  • fPar[1] - czas odczytu (patrz opis ustawienia czasu w Kod 5)
  • fPar[2] - pozycja X na scenerii
  • fPar[3] - pozycja Y na scenerii (wysokość)
  • fPar[4] - pozycja Z na scenerii
  • fPar[5] - składowa prędkości ruchu w osi X
  • fPar[6] - składowa prędkości ruchu w osi Y
  • fPar[7] - składowa prędkości ruchu w osi Z
  • fPar[8] - składowa przyspieszenia w osi X

Ze względu na fazę eksperymentalną tej funkcji obecnie nie są wyliczane składowe prędkości pojazdu, a prędkość jest wpisywana do fPar[5] (fPar[6] i fPar[7] są zerowe), dlatego również nie ma pozostałych składowych przyspieszenia. Sytuację tę można powiązać z ilością parametrów (czyli 9), zbyt małą jak na umieszczenie trzech składowych przyspieszenia. Jeśli składowe będą liczone, parametrów będzie co najmniej 11.

Za zestawem wartości znajduje się napis określający nazwę pojazdu. Nazwa ta zaczyna się od bajtu cString[4*iPar[0]] (zawiera on licznik).

Kod 8 - zajętość toru

Do czasu upowszechnienia się obsługi odcinków izolowanych zostało wprowadzone informowanie o zajętości poszczególnych torów. Symulator wysyła taką ramkę w momencie gdy pierwszy pojazd zostanie dodany do toru, a tor ma nazwę inną niż none. Dostępne od wersji 251.

Od wersji 394 ramka przychodząca z tym kodem spowoduje wysłanie ramek zajętości dla wszystkich zajętych torów. Jest to przydatne w celu aktualizacji zajętości w momencie podłączenia do serwera. Wcześniej ramki przychodzące nie były obsługiwane.

Kod 9 - zwolnienie toru

Działa podobnie jak poprzedni, ale jest przesyłany, gdy z toru zjedzie ostatni pojazd. Dla krótkich odcinków (krótszych niż rozstaw osi/wózków) może być generowany wielokrotnie. Dostępny od wersji 391.

Od wersji 417 ramka przychodząca z tym kodem spowoduje wysłanie ramek zajętości (kod 11) albo zwolnienia (kod 10; do wersji 439 – 12) dla wszystkich odcinków izolowanych. Od wersji 440 na koniec listy wysyłana jest ramka z kodem 10 dla "none".

Kod 10 - badanie zajętości odcinka izolowanego

Wysłanie takiej ramki do Symulatora w wersji co najmniej 417 spowoduje odpowiedź w postaci ramki zajętości (kod 11) dla wskazanego odcinka izolowanego albo ramki zwolnienia (kod 10; do wersji 439 kod 12 dla "none").

Kod 11 - zajętość odcinka izolowanego

Od wersji 417 ramka wysyłana będzie w momencie gdy jakakolwiek oś pojazdu znajdzie się na niezajętym wcześniej odcinku izolowanym.

Kod 12 - zwolnienie odcinka izolowanego

Działa podobnie jak poprzedni, ale jest przesyłany, gdy z odcinka izolowanego zjedzie ostatnia oś. Dostępny od wersji 417 do 439. Od wersji 440 zastąpiony kodem 10.

Kod 13 - ustawienie uszkodzenia pojazdu

Ustawia flagę bitową uszkodzenia pojazdu o podanej nazwie. Jeśli nazwą jest * wywoływany jest pojazd sterowany przez gracza. Parametry:

  • 1 bajt - flaga
  • 2 bajt - długość tekstu
  • pozostałe - nazwa pojazdu

Komunikacja po TCP/IP

Do komunikacji po sieci wykorzystywana jest biblioteka ZeroMQ. Nie ma możliwości ręcznego wysyłania pakietów, tylko należy wykorzystywać protokół komunikacyjny ZMTP 3.0 (wykorzystywana wersja biblioteki 4.4.3).

Konfiguracja

W EU07.ini dostępne są następujące opcje:

  • network <adres IP> <port> <identyfikator>

Identyfikator musi być unikalną zestawem znaków, wg których serwer będzie wiedział do którego z klientów należy wysyłać informacje. Można ustawić opcję "auto", która spowoduje ustawienie losowego identyfikatora (opcja zalecana). Dla komunikacji z serwerem uruchomionym lokalnie wpis wygląda nastepująco: network 127.0.0.1 5555 auto

Protokół

Podobnie jak dla komunikacji za pomocą WM_COPYDATA komunikacja odbywa się za pomocą komend sterujących. Każda komenda jest wysyłana w postaci wiadomości wieloczęściowej, z następującą kolejnością ramek.

  • komenda - int (4 bajtów)
  • kolejne ramki z danymi o liczbie wymaganej kodem - w zależności od potrzeby string, int, float lub dane serializowane.

Parametry ramek

Kod 0 - wersja protokołu

int int
0 - zapytanie
1 - odpowiedź wersja protkołu

Kod 1 - informacja o wersji protokołu

  • int - numer wersji

Kod 2 - zdalne wywołanie eventu

  • string - nazwa eventu

Kod 3 - zdalne wywołanie polecenia dla pojazdu

  • float - parametr 1
  • float - parametr 2
  • string - komenda
  • string - nazwa pojazdu

Kod 4 - zapytanie o stan toru

  • string - nazwa toru - w przypadku zapytania o wszystkie nazwą jest "*"

Kod 5 - informacja torów

  • protobuf

Kod 6 - zapytanie o stan odcinka izolowanego

  • string - nazwa odcinka - w przypadku zapytania o wszystkie nazwą jest "*"

Kod 7 - informacja o stanie odcinków izolowanych

  • protobuf

Kod 8 - ustawienie parametrów symulacji

  • int - flaga parametru: 1 - ustawienie czasu, 2 - ustawienie pauzy
  • float - czas / int - flaga pauzy

Kod 9 - informacja o stanie pojazdu

  • string - nazwa pojazdu, jeśli "*" to wszystkie, jeśli "player" to pojazd gracza

Kod 10 - informacja o stanie pociągu

  • string - nazwa zgodna z rozkładem jazdy

Kod 11 - informacja o stanie pojazdów

  • protobuf