Komunikacja z programami trzecimi
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.
Spis treści
- 1 Włączenie komunikacji
- 2 Ramka WM_COPYDATA
- 3 Ramka danych
- 4 Kody ramek danych
- 4.1 Kod 0 - wersja programu
- 4.2 Kod 1 - nazwa scenerii
- 4.3 Kod 2 - wykonanie eventu
- 4.4 Kod 3 - rozkaz dla AI
- 4.5 Kod 4 - test zajętości toru
- 4.6 Kod 5 - ustawienie czasu itp.
- 4.7 Kod 6 - zapytanie o pojazd
- 4.8 Kod 7 - parametry ruchu pojazdu
- 4.9 Kod 8 - zajętość toru
- 4.10 Kod 9 - zwolnienie toru
- 4.11 Kod 10 - badanie zajętości odcinka izolowanego
- 4.12 Kod 11 - zajętość odcinka izolowanego
- 4.13 Kod 12 - zwolnienie odcinka izolowanego
- 4.14 Kod 13 - ustawienie uszkodzenia pojazdu
- 5 Komunikacja po TCP/IP
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 danymio liczbie wymaganej kodem - w zależności od potrzeby string, int, float lub dane serializowane przez Protocol Buffers 3.
Parametry ramek
Kod 0 - zapytanie o wersję
- bez parametrów
kod 1
- bez parametrów
Kod 2
- string - nazwa eventu
Kod 3
- float - parametr 1
- float - parametr 2
- string - komenda
- string - nazwa pojazdu
Kod 4
- string - nazwa toru
Kod 5
- int - flaga parametru: 1 - ustawienie czasu, 2 - ustawienie pauzy
- float - czas / int - flaga pauzy
Kod 6
- string - nazwa pojazdu, jeśli * to wybrany jest pojazd gracza
Kod 7
Kod 8
- brak parametrów
Kod 9
- brak parametrów
Kod 10
- string - nazwa odcinka izolowanego
Kod 11
Nie używana
Kod 12
- brak parametrów
Kod 13
- string - nazwa pojazdu
- 1 bajt - flaga uszkodzeń