Jak obsługiwać stan początkowy w architekturze sterowanej zdarzeniami?

33

W architekturze sterowanej zdarzeniami każdy komponent działa tylko wtedy, gdy zdarzenie jest wysyłane przez system.

Wyobraź sobie hipotetyczny samochód z pedałem hamulca i światłem hamowania.

  • Światło stop włącza się, gdy odbiera zdarzenie brak hamulca , i gaśnie, gdy odbiera zdarzenie brak hamulca .
  • Pedał hamulca wysyła zdarzenie brake_on , gdy jest wciśnięty, i zdarzenie hamulca_off , gdy jest zwolnione.

Wszystko dobrze i dobrze, dopóki nie pojawi się sytuacja, w której samochód zostanie włączony, gdy pedał hamulca jest już wciśnięty . Ponieważ światło hamowania nigdy nie otrzymało zdarzenia brak_wyłączenia , pozostanie wyłączone - wyraźnie niepożądana sytuacja. Domyślne włączenie światła stopu tylko odwraca sytuację.

Co można zrobić, aby rozwiązać ten „problem stanu początkowego”?

EDYCJA: Dziękuję za wszystkie odpowiedzi. Moje pytanie nie dotyczyło prawdziwego samochodu. W samochodach rozwiązali ten problem poprzez ciągłe wysyłanie stanu - dlatego nie ma problemu z uruchomieniem w tej domenie. W mojej domenie oprogramowania to rozwiązanie wymagałoby wielu niepotrzebnych cykli procesora.

EDYCJA 2: Oprócz odpowiedzi @ gbjbaanb idę na system, w którym:

  • hipotetyczny pedał hamulca, po inicjalizacji, wysyła zdarzenie ze swoim stanem, oraz
  • hipotetyczne światło stopu po inicjalizacji wysyła zdarzenie żądające zdarzenia stanu z pedału hamulca.

Dzięki temu rozwiązaniu nie ma zależności między komponentami, żadnych warunków wyścigu, żadnych kolejek komunikatów, które mogłyby się zestarzeć, ani żadnych komponentów „wzorcowych”.

Frank Kusters
źródło
2
Pierwszą rzeczą, jaka przychodzi na myśl, jest wygenerowanie zdarzenia „syntetycznego” (nazwij go initialize), które zawiera potrzebne dane z czujnika.
msw
Czy pedał nie powinien wysyłać zdarzenia brake_pedal_on, a faktyczny hamulec powinien wysyłać zdarzenie brake_on? Nie chciałbym, aby moje światło stopu zaświeciło się, gdyby hamulec nie działał.
bdsl
3
Czy wspominałem, że był to hipotetyczny przykład? :-) Jest bardzo uproszczone, aby pytanie było krótkie i rzeczowe.
Frank Kusters

Odpowiedzi:

32

Można to zrobić na wiele sposobów, ale wolę, aby system oparty na wiadomościach był możliwie jak najbardziej rozdzielony. Oznacza to, że cały system nie może odczytać stanu żadnego składnika, ani żaden składnik nie odczytał stanu żadnego innego (ponieważ w ten sposób spoczywają zależności zależności).

Tak więc, podczas gdy działający system będzie dbał o siebie, potrzebujemy sposobu, aby każdy komponent sam się uruchomił, a my już mamy coś takiego w rejestracji komponentu, tj. Przy uruchomieniu system podstawowy musi informować każdy komponent, że jest teraz zarejestrowany (lub poprosi każdy komponent o zwrócenie jego danych, aby można go było zarejestrować). Jest to etap, na którym komponent może wykonywać swoje zadania uruchamiania i może wysyłać wiadomości tak, jak w normalnej pracy.

Tak więc pedał hamulca, gdy zapłon zostanie uruchomiony, otrzyma komunikat rejestracyjny / kontrolny od kierownictwa samochodu i zwróci nie tylko komunikat „Jestem tutaj i pracuję”, ale następnie sprawdzi swój stan i wyśle komunikaty dla tego stanu (np. komunikat z wciśniętym pedałem).

Problem staje się wtedy zależny od uruchomienia, tak jakby światło stopu nie zostało jeszcze zarejestrowane, wówczas nie otrzyma komunikatu, ale można to łatwo rozwiązać, ustawiając w kolejce wszystkie te komunikaty, aż system podstawowy zakończy procedurę uruchamiania, rejestracji i sprawdzania .

Największą zaletą jest to, że nie jest wymagany specjalny kod do obsługi inicjalizacji, z wyjątkiem tego, że musisz już napisać (ok, jeśli wysyłanie wiadomości o zdarzeniach dotyczących pedału hamulca odbywa się w module obsługi pedału hamulca, będziesz musiał to również wywołać podczas inicjalizacji , ale zwykle nie stanowi to problemu, chyba że napisałeś ten kod silnie powiązany z logiką modułu obsługi) i nie ma interakcji między komponentami oprócz tych, które już do siebie wysyłają w normalny sposób. Z tego powodu architektury przekazywania wiadomości są bardzo dobre!

gbjbaanb
źródło
1
Podoba mi się twoja odpowiedź, ponieważ utrzymuje wszystkie komponenty oddzielone - był to najważniejszy powód, aby wybrać tę architekturę. Jednak obecnie nie ma prawdziwego komponentu „master”, który decydowałby, że system jest w stanie „zainicjalizowanym” - wszystko po prostu zaczyna działać. W rezultacie pojawia się problem w moim pytaniu. Gdy master zdecyduje, że system działa, może wysłać zdarzenie „inicjalizacji systemu” do wszystkich komponentów, po czym każdy komponent zaczyna rozgłaszać swój stan. Problem rozwiązany. Dziękuję Ci! (Teraz mam problem z decyzją, czy system zostanie zainicjowany ...)
Frank Kusters,
Co powiesz na to, aby dyspozytor aktualizacji statusu śledził najnowszą aktualizację otrzymaną od każdego obiektu i czy przy każdym otrzymaniu nowego żądania subskrypcji wysyła nowemu abonentowi najnowsze aktualizacje, które otrzymał od zarejestrowanych źródeł zdarzeń?
supercat
W takim przypadku musisz także śledzić, kiedy wydarzenia wygasają. Nie wszystkie zdarzenia pozwalają zachować na zawsze wszelkie nowe komponenty, które mogą się zarejestrować.
Frank Kusters,
@ spaceknarf cóż, w przypadku, gdy „wszystko zaczyna się uruchamiać”, nie możesz zbudować zależności w komponentach, więc pedał zaczyna się po świetle, musisz tylko uruchomić je w tej kolejności, choć wyobrażam sobie, że coś je uruchamia, więc biegnij je w „właściwej” kolejności (np. skrypty startowe Linuksa przed systemd, gdzie usługa do uruchomienia nazywa się 1.xxx, a druga to 2.xxx itp.).
gbjbaanb
Skrypty o takiej kolejności są delikatne. Zawiera wiele ukrytych zależności. Zamiast tego zastanawiałem się, czy masz „główny” komponent, który ma statycznie skonfigurowaną listę komponentów, które powinny być uruchomione (jak wspomniano w @Lie Ryan), a następnie może wyemitować zdarzenie „gotowe” po załadowaniu wszystkich tych komponentów. W odpowiedzi na to wszystkie elementy nadają swój stan początkowy.
Frank Kusters,
4

Możesz mieć zdarzenie inicjalizacji, które odpowiednio ustawia stany podczas ładowania / uruchamiania. Może to być pożądane w przypadku prostych systemów lub programów niezawierających wielu elementów sprzętowych, jednak w przypadku bardziej skomplikowanych systemów z wieloma fizycznymi komponentami, ponieważ narażasz się na takie samo ryzyko, jak w ogóle nie inicjowanie - jeśli zdarzenie „hamowania” zostanie pominięte lub utracone podczas komunikacji system (na przykład system oparty na CAN), możesz przypadkowo ustawić swój system do tyłu, tak jakbyś uruchomił go z wciśniętym hamulcem. Im więcej kontrolerów możesz mieć, na przykład z samochodem, tym większe prawdopodobieństwo, że coś zostanie pominięte.

Aby to uwzględnić, logika „hamulec włączony” może wielokrotnie wysyłać zdarzenia „hamulec włączony”. Może co 1/100 sekundy lub coś takiego. Twój kod zawierający mózg może nasłuchiwać i wywoływać „hamowanie” podczas ich odbierania. Po 1/10 sek. Nieotrzymania sygnałów „hamowanie włączone” wyzwala wewnętrzne zdarzenie „brak hamulca”.

Różne zdarzenia będą miały znacznie różne wymagania dotyczące czasu. W samochodzie twoje światło stopu musi być znacznie szybsze niż powiedz, że paliwo kontrolne paliwo (gdzie opóźnienie wielosekundowe jest prawdopodobnie dopuszczalne) lub inne mniej ważne układy.

Złożoność twojego systemu fizycznego decyduje o tym, które z tych podejść jest bardziej odpowiednie. Biorąc pod uwagę, że twoim przykładem jest pojazd, prawdopodobnie chciałbyś czegoś podobnego do tego drugiego.

Tak czy inaczej, w systemie fizycznym NIE chcesz polegać na tym, że pojedyncze zdarzenie zostanie poprawnie odebrane / przetworzone. Z tego powodu połączone mikrokontrolery w systemie sieciowym często mają limit czasu „Jestem żywy”.

kraina krańca
źródło
w systemie fizycznym poprowadziłbyś drut i użyłby logiki binarnej: WYSOKI jest wciśnięty hamulec, a NISKI nie wciśnięty hamulec
maniak zapadkowy
@ratchetfreak istnieje wiele możliwości tego rodzaju rzeczy. Być może przełącznik sobie z tym poradzi. Istnieje wiele innych zdarzeń systemowych, które nie są obsługiwane w tak prosty sposób.
kraina krańca
1

W tym przypadku nie modelowałbym hamulca jako prostego włączania / wyłączania. Zamiast tego wysyłałbym zdarzenia „ciśnienia hamowania”. Na przykład ciśnienie 0 oznaczałoby wyłączenie, a ciśnienie 100 byłoby całkowicie obniżone. System (węzeł) stale wysyła zdarzenia ciśnienia przerwania (w określonych odstępach czasu) do kontrolera (-ów) w razie potrzeby.

Gdy system został uruchomiony, zaczął odbierać zdarzenia ciśnienia, dopóki nie został wyłączony.

Jon Raynor
źródło
1

Jeśli jedynym sposobem przekazywania informacji o stanie są zdarzenia, masz kłopoty. Zamiast tego musisz być w stanie:

  1. sprawdzić aktualny stan pedału hamulca, oraz
  2. zarejestrować zdarzenia „zmiany stanu” z pedału hamulca.

Światło stop może być postrzegane jako obserwator pedału hamulca. Innymi słowy, pedał hamulca nie wie nic o świetle hamowania i może działać bez niego. (Oznacza to, że każde pojęcie pedału hamulca proaktywnie wysyłającego zdarzenie „stanu początkowego” do światła hamowania jest niewłaściwe).

Po uruchomieniu systemu światło hamowania rejestruje się w pedale hamulca, aby otrzymywać powiadomienia o hamowaniu, a także odczytuje aktualny stan pedału hamulca i włącza się lub wyłącza.

Następnie powiadomienia o hamowaniu można wdrożyć na jeden z trzech sposobów:

  1. jako bezparametrowe zdarzenia „zmiana stanu pedału hamulca”
  2. gdy para „pedał hamowania jest teraz wciśnięty” i „pedał hamulca jest teraz zwolniony” zdarzenia
  3. jako zdarzenie „nowego stanu zerwania pedału” z parametrem „wciśnięty” lub „zwolniony”.

Wolę pierwsze podejście, co oznacza, że ​​po otrzymaniu powiadomienia światło stopu po prostu zrobi to, co już wie, jak to zrobić: odczytuje aktualny stan pedału hamulca i sam się włącza lub wyłącza.

Mike Nakis
źródło
0

W systemie opartym na zdarzeniach (z którego obecnie korzystam i które uwielbiam) uważam, że ważne jest, aby zachować możliwie jak największą niezależność. Mając to na uwadze, zajrzyjmy od razu.

Ważne jest, aby mieć stan domyślny. Światło hamowania przyjmie domyślny stan „wyłączony”, a pedał hamulca przyjmie domyślny stan „w górę”. Wszelkie późniejsze zmiany byłyby wydarzeniem.

Teraz, aby odpowiedzieć na twoje pytanie. Wyobraź sobie, że twój pedał hamulca został zainicjowany i wciśnięty, zdarzenie zapala się, ale jeszcze nie ma świateł stop, które je przyjmą. Odkryłem, że najłatwiej jest oddzielić tworzenie obiektów (w których inicjowane byłyby detektory zdarzeń) jako osobny krok przed zainicjowaniem jakiejkolwiek logiki. Zapobiegnie to warunkom wyścigowym, które opisałeś.

Uważam również, że niewygodne jest używanie dwóch różnych zdarzeń w celu uzyskania tego samego efektu . brake_offi brake_onmożna je uprościć e_brakeza pomocą parametru bool on. W ten sposób możesz uprościć swoje wydarzenia, dodając dane pomocnicze.

Thebluefish
źródło
0

To, czego potrzebujesz, to zdarzenie transmisji i skrzynki odbiorcze wiadomości. Transmisja to komunikat publikowany nieokreślonej liczbie słuchaczy. Składnik może zasubskrybować wydarzenia rozgłoszeniowe, aby odbierać tylko zdarzenia, które są nim zainteresowane. Zapewnia to oddzielenie, ponieważ nadawca nie musi wiedzieć, kim są odbiorcy. Tabela subskrypcji musi zostać skonfigurowana statycznie podczas instalacji komponentu (zamiast podczas inicjowania). Skrzynka odbiorcza jest częścią routera wiadomości, który działa jako bufor do przechowywania wiadomości, gdy docelowy komponent jest w trybie offline.

Korzystanie z faktur wiąże się z jednym problemem, jakim jest rozmiar skrzynki odbiorczej. Nie chcesz, aby system przechowywał rosnącą liczbę komunikatów dla komponentów, które nigdy nie będą już w trybie online. Jest to ważne zwłaszcza w przypadku systemu osadzonego o ścisłych ograniczeniach pamięci. Aby pokonać limit wielkości skrzynki odbiorczej, wszystkie rozgłaszane wiadomości muszą przestrzegać kilku zasad. Reguły są następujące:

  1. każde wydarzenie wymaga nazwy
  2. w dowolnym momencie nadawca zdarzenia transmisji może mieć tylko jedną aktywną transmisję o określonej nazwie
  3. efekt wywołany przez zdarzenie musi być idempotentny

Nazwa emisji musi zostać zadeklarowana podczas instalacji komponentu. Jeśli komponent wysyła drugą transmisję o tej samej nazwie, zanim odbiornik przetworzy poprzednią, nowa transmisja zastępuje poprzednią. Teraz możesz mieć limit wielkości statycznej skrzynki odbiorczej, który nigdy nie przekroczy określonego rozmiaru i może być wstępnie obliczony na podstawie tabel subskrypcji.

Wreszcie potrzebujesz również archiwum emisji. Archiwum transmisji to tabela zawierająca ostatnie zdarzenie z każdej nazwy emisji. Nowo zainstalowane komponenty będą miały wstępnie wypełnioną skrzynkę odbiorczą z wiadomościami z archiwum emisji. Podobnie jak skrzynka odbiorcza wiadomości, archiwum emisji może mieć również rozmiar statyczny.

Dodatkowo, aby poradzić sobie z sytuacją, w której sam router wiadomości jest w trybie offline, potrzebne są również skrzynki nadawcze wiadomości. Skrzynka nadawcza wiadomości jest częścią komponentu, który tymczasowo przechowuje wiadomość wychodzącą.

Lie Ryan
źródło