Elektroda.pl
Elektroda.pl
X
Proszę, dodaj wyjątek dla www.elektroda.pl do Adblock.
Dzięki temu, że oglądasz reklamy, wspierasz portal i użytkowników.

[C] - Funkcje nieblokujące zależne czasowo, maszyny stanów, RTOS

Tomq 31 Maj 2014 10:06 9687 77
  • #31
    Freddie Chopin
    Specjalista - Mikrokontrolery
    To chyba aż zrobię kiedyś podobny test u siebie na ARM - masz jakiś przykładowy kod który mógłbym od razu wrzucić? (;

    4\/3!!
  • #32
    Jado_one
    Poziom 22  
    W zasadzie kodu takiego, co bez przeróbek zadziała to nie mam - każdy fragment ma różne zależności zdefiniowane w innych plikach/procedurach.
    Ale dla przykładu mogę wrzucić jedną prostą maszynę stanów - część rzeczy można wykomentować, stworzyć jakieś swoje fikcyjne zmienne, itp...

    Kod: c
    Zaloguj się, aby zobaczyć kod

    Ale podejrzewam, że łatwiej będzie coś zrobić na szybko od nowa ;-)
  • #33
    Tomq
    Poziom 38  
    Prosiłbym o wspólne przeanalizowanie tego kodu i poprawienie mnie w miejscach gdzie się mylę. Kod napisany przez gaskoin:
    Kod: c
    Zaloguj się, aby zobaczyć kod



    Analiza kodu:
    Kod: c
    Zaloguj się, aby zobaczyć kod

    Zdefiniowanie nowego typu zmiennych o nazwie Blower, typ ten jest w rzeczywistości strukturą Blower.


    Kod: c
    Zaloguj się, aby zobaczyć kod

    Zdefiniowanie nowego typu zmiennych o nazwie BlowerState. Typ ten jest strukturą zawierając w sobie wskaźnik na funkcje, którego argumentem jest wskaźnik na strukturę Blower.

    Kod: c
    Zaloguj się, aby zobaczyć kod

    Zdefiniowanie struktury Blower. Struktura ta zawiera zmienną typu integer oraz wskaźnik na typ strukturalny BlowerState.

    Kod: c
    Zaloguj się, aby zobaczyć kod

    Deklaracje funkcji, których argumentami są wskaźniki na struktury typu Blower.


    Kod: c
    Zaloguj się, aby zobaczyć kod

    Zadeklarowanie zmiennych strukturalnych BlowerState. Każda taka zmienna zawiera w sobie adres funkcji, które zostały zdefiniowane wyżej.


    Kod: c
    Zaloguj się, aby zobaczyć kod

    Funkcja main, w której zadeklarowaliśmy zmienną strukturalną o nazwie mySuperDuperBlower. Przypisie wartości pól w strukturze.
    Pętla która dziesięć razy przypisuje do pola state funkcję, której argument pobiera ze struktury mySuperDuperBlower. Więc tutaj wywoływana jest funkcja handle o argumecie &idleState. Funkcja ta zwraca adres "&blower_idle_state_handler", a więc de facto wykonuje się druga funkcja
    przypisująca do pola .power wartość 10, wyświetlająca "jestem w stanie 1 i mam moc 10" oraz zmieniająca pole state na &hyperPowerState.

    Teraz więc przy następnej iteracji pętli pole state będzie zawierało adres hyperPowerState, a więc skoczymy pod adres funkcji blower_super_duper_hiper_state_handler i przypiszemy do pola .power wartość 1000, wypiszemy jaka jest wartość tego pola i przypiszemy do pole state kolejny adres.



    Ok. Tylko dla mnie wygląda to jak automatyczne przechodzenie z jednego stanu w inny. A co jeśli chciałbym, żeby ta maszyna reagowała na rzeczywiste wydarzenia, np odczytano wartość ADC poniżej wyznaczonego progu, albo wciśnięto przycisk?
  • #34
    Jado_one
    Poziom 22  
    Tomq napisał:

    Ok. Tylko dla mnie wygląda to jak automatyczne przechodzenie z jednego stanu w inny. A co jeśli chciałbym, żeby ta maszyna reagowała na rzeczywiste wydarzenia, np odczytano wartość ADC poniżej wyznaczonego progu, albo wciśnięto przycisk?

    Po prostu trzeba dodać warunki, które muszą zostać sprawdzone przed zmianą stanu maszyny np.:
    Kod: c
    Zaloguj się, aby zobaczyć kod


    Przy jednym klawiszu to można zrobić w ten sposób, ale przy większej ilości (a zwłaszcza z podmenus) to robi się oddzielną maszynę stanów na obsługę klawiszy.
  • #35
    Tomq
    Poziom 38  
    Cytat:
    Po prostu trzeba dodać warunki, które muszą zostać sprawdzone przed zmianą stanu maszyny

    Czyli musimy użyć zmiennych globalnych (klawisz, ADC_value).


    Cytat:
    Przy jednym klawiszu to można zrobić w ten sposób, ale przy większej ilości (a zwłaszcza z podmenus) to robi się oddzielną maszynę stanów na obsługę klawiszy.

    A jak skomunikować ze sobą kilka maszyn stanu? Zmiennymi globalnymi? Czy po prostu używać wskaźników na te same funkcje? Wiem, że to wiele zależy od konkretnego projektu, ale chciałbym zobaczyć jakieś przykładowe powiązania między np dwiema różnymi maszynami stanu.

    Ewentualnie można robić jedną dużą maszynę stanu (klawiatura). Stanami byłyby różne odczyty klawiszy. Dany stan zawierałby tekst który należy wyświetlić na wyświetlaczu i parametr który należałoby zmienić (poprzez wskaźnik), lub funkcja jaką należałoby wywołać (np. wyślij poprzez rs232/gsm, włącz_coś_tam). Stan Idle wtedy gdy żaden klawisz nie wciśnięty i w tym idlu oczekwiało by się na wciśnięcie klawisza oraz można by przełączać inne funkcje (np przelicz_temperature, zapisz okresowy log na kartę pamięci, etc). W tym wypadku Idle też musiałoby być maszyną stanu (wewnątrz większej maszyny jaką jest klawiatura).

    Ma sens powyższa konstrukcja?
  • Pomocny post
    #36
    Jado_one
    Poziom 22  
    Tomq napisał:
    Cytat:
    Po prostu trzeba dodać warunki, które muszą zostać sprawdzone przed zmianą stanu maszyny

    Czyli musimy użyć zmiennych globalnych (klawisz, ADC_value).


    Jeśli o mnie chodzi to nie mam oporów przed używaniem zmiennych globalnych ;-)

    Tomq napisał:
    A jak skomunikować ze sobą kilka maszyn stanu? Zmiennymi globalnymi? Czy po prostu używać wskaźników na te same funkcje? Wiem, że to wiele zależy od konkretnego projektu, ale chciałbym zobaczyć jakieś przykładowe powiązania między np dwiema różnymi maszynami stanu.


    Kilka postów wyżej jest przykład mojej maszyny stanów (Sound_StateMachine).
    Tam własnie widać jak maszyny komunikują się ze sobą - po prostu ładuje się pewne wartości do zmiennych/buforów i wywołuje określony stan (innej) maszyny stanów, która "robi swoje".
    Być może w sposobie pokazanym przez kolegę Gaskoin'a da się to inaczej zrobić - zobaczymy co powie :-)

    Tomq napisał:

    Ewentualnie można robić jedną dużą maszynę stanu (klawiatura). Stanami byłyby różne odczyty klawiszy. Dany stan zawierałby tekst który należy wyświetlić na wyświetlaczu i parametr który należałoby zmienić (poprzez wskaźnik), lub funkcja jaką należałoby wywołać (np. wyślij poprzez rs232/gsm, włącz_coś_tam). Stan Idle wtedy gdy żaden klawisz nie wciśnięty i w tym idlu oczekwiało by się na wciśnięcie klawisza oraz można by przełączać inne funkcje (np przelicz_temperature, zapisz okresowy log na kartę pamięci, etc). W tym wypadku Idle też musiałoby być maszyną stanu (wewnątrz większej maszyny jaką jest klawiatura).

    Ma sens powyższa konstrukcja?

    Ja u siebie po prostu wychodzę z maszyny stanów do pętli głównej - i w stanie idle, następuje tylko wejście do stanu zero (idle) i natychmiastowe wyjście.
    Dopiero zmiana stanu !=0 powoduje "wyzwolenie maszyny stanów".
    Jeśli byś miał zmienną której numer odpowiadałby numerowi wciśniętego klawisza,
    a stan zero odpowiadałby stanowi, kiedy żaden klawisz nie jest wciśnięty, to łatwo byłoby zrealizować taką obsługę. Ale to dotyczy tradycyjnego switch/case przypadku - jak to jest w przypadku przeładowywanych wskaźników, to na chwilę bieżącą nie wiem.
    Może koledzy coś ciekawego w tej materii podpowiedzą jeszcze :-)
  • #37
    Tomq
    Poziom 38  
    Nie rozumiem jeszcze jednej rzeczy, chyba nie do końca zrozumiałem przykład gaskoin.
    Kod: c
    Zaloguj się, aby zobaczyć kod


    Jak to możliwe, że "BlowerState" jest zmienną strukturalną (bo po zamknięciu definicji struktury możemy deklarować jej zmienne i tak właśnie tu jest zrobione, za nawiasem "}" stoi nazwa BlowerState)
    Kod: c
    Zaloguj się, aby zobaczyć kod


    A jednocześnie jest ono strukturą (bo za pomocą nazwy BlowerState deklarujemy inne struktury):
    Kod: c
    Zaloguj się, aby zobaczyć kod


    Jak dla mnie to powinno to wyglądać tak:

    Kod: c
    Zaloguj się, aby zobaczyć kod


    Albo tak:
    Kod: c
    Zaloguj się, aby zobaczyć kod



    Nie neguję poprawności kodu, bo sprawdziłem go w online'owym kompilatorze ( https://ideone.com/ ), jednak dla sposób w jaki jest to zapisane nie jest zrozumiały, dlatego prosiłbym o wyjaśnienie tej kwestii.

    ...............................................
    EDIT:
    Chyba tego nie ogarnę.
    Zrobiłem skoki na wskaznikach na funkcje, oceńcie proszę czy to się trzyma kupy, czy wyhodowałem potworka.

    Kod: c
    Zaloguj się, aby zobaczyć kod


    ............................
    EDIT2
    Tu jest ładna, prosta maszynka stanów z wykorzystaniem struktur: https://www.elektroda.pl/rtvforum/viewtopic.php?p=5491796#5491796
  • #38
    Jado_one
    Poziom 22  
    Jeśli Ci bardziej pasuje klasyczna maszyna stanów, typu switch/case, to
    idź w tym kierunku - ja tak robię i nie narzekam :-)
    Bo Koledzy zapewne na weekend wyjechali i wcześniej niż przed końcem weekendu Ci nie odpowiedzą ;-)

    Najważniejsze, żeby ustalić algorytm działania - co się ma dziać w którym momencie.
    Potem trzeba w/g tego dobrać stany maszyny stanów.

    Ja własnie siedzę nad maszyną stanów do obsługi RFM22B - na początek trzeba trochę pokombinować, zastanowić jak to najlepiej zrealizować - zanim się zacznie pisać.
  • #39
    Freddie Chopin
    Specjalista - Mikrokontrolery
    Tomq napisał:
    Pętla która dziesięć razy przypisuje do pola state funkcję, której argument pobiera ze struktury mySuperDuperBlower.

    Tam nie ma żadnego przypisania.

    Tomq napisał:
    Jak to możliwe, że "BlowerState" jest zmienną strukturalną (bo po zamknięciu definicji struktury możemy deklarować jej zmienne i tak właśnie tu jest zrobione, za nawiasem "}" stoi nazwa BlowerState)

    Struktura jest "anonimowa". Zapis o który pytasz w skrócie wygląda tak:

    typedef struct { ... } NazwaTypu;

    W wersji "nie-anonimowej":

    typedef struct NazwaStruktury { ... } NazwaTypu;

    W wersji "rozbitej":

    struct NazwaStruktury { ... };
    typedef struct NazwaStruktury NazwaTypu;

    Gaskoin użył takiej konstrukcji, ponieważ "typ struktury" jest w zasadzie zbędny.

    W zasadzie to są podstawy języka C.

    4\/3!!
  • #40
    Tomq
    Poziom 38  
    Kod: c
    Zaloguj się, aby zobaczyć kod


    Freddie Chopin napisał:
    Tam nie ma żadnego przypisania.

    Racja. To tylko wyłuskanie wskaźnika i selekcja pola. Więc w tym miejscu 10 razy wykonujemy funkcje która znajduje się pod tym adresem. A adres zmienia się w zależności od właśnie wybranej funkcji. Funkcja aktualnie wykonująca się "zostawia po sobie" w miejscu w którym była adres do innej funkcji i przy kolejnej iteracji ta właśnie kolejna funkcja jest wykonywana.
  • Pomocny post
    #41
    gaskoin
    Poziom 38  
    Nie byłem w domu od czwartku i nie miałem dostępu do neta.

    Tomq napisał:
    A jak skomunikować ze sobą kilka maszyn stanu? Zmiennymi globalnymi? Czy po prostu używać wskaźników na te same funkcje? Wiem, że to wiele zależy od konkretnego projektu, ale chciałbym zobaczyć jakieś przykładowe powiązania między np dwiema różnymi maszynami stanu.


    Co znaczy skomunikować maszyny stanu ? W moim przykładzie masz dmuchawę (Blower), która ma swój określony kontekst + stan. Stany posiadają konkretne handlery, czyli funkcje obsługujące dany stan dmuchawy. Jak nie wieje, to moc na max, jak jest na max, to trochę zmniejsz itd. Maszyna stanów to tylko fakt, że przechodzimy z jednego stanu w drugi i tyle. Pytanie jakie powinno paść to chyba raczej - jak dany stan nakarmić danymi zewnętrznymi np, temperatura ?

    Tomq napisał:
    Ewentualnie można robić jedną dużą maszynę stanu (klawiatura). Stanami byłyby różne odczyty klawiszy. Dany stan zawierałby tekst który należy wyświetlić na wyświetlaczu i parametr który należałoby zmienić (poprzez wskaźnik), lub funkcja jaką należałoby wywołać (np. wyślij poprzez rs232/gsm, włącz_coś_tam). Stan Idle wtedy gdy żaden klawisz nie wciśnięty i w tym idlu oczekwiało by się na wciśnięcie klawisza oraz można by przełączać inne funkcje (np przelicz_temperature, zapisz okresowy log na kartę pamięci, etc). W tym wypadku Idle też musiałoby być maszyną stanu (wewnątrz większej maszyny jaką jest klawiatura).

    Ma sens powyższa konstrukcja?


    Poczytaj o hierarchicznych maszynach stanów.
    Spójrz na mój przykład. Klawiatura to nie maszyna stanu, tylko obiekt posiadający swój własny kontekst, którym jest między innymi jej stan. Nie ma sensu też wciskać bez sensu maszynę stanów gdzie popadnie. Odczyt z klawiatury może być zrealizowany na timerze/ADC + mapa z inta (numer wciśniętego klawisza) na stringa. Do klawiatury (też zależy w sumie jakiej) lepsze jest coś w stylu event source. Naciskasz klawisz -> wywołujesz event "naciśnięto klawisz detonacji urządzenia" -> łapiesz event i wołasz jego obsługę.

    Tomq napisał:
    Tu jest ładna, prosta maszynka stanów z wykorzystaniem struktur: https://www.elektroda.pl/rtvforum/viewtopic.php?p=5491796#5491796


    W moim przykładzie nie chodzi o to żeby sobie użyć struktur, tylko żeby pokazać ideę działania stanu bez sprawdzania w jakim stanie się jest. W praktyce przecież nie obchodzi nas w jakim stanie jesteśmy, tylko to żeby wykonać jakąś akcję na tym stanie. Przykład z jednej strony jest ok bo struktury wiążą konteksty. Ma to taką przewagę nad robieniem zmiennych globalnych (notabene struktura też może być jako zmienna globalna), że zmienne są ze sobą logicznie powiązane. Mimo wszystko podany przykład to dalej switch-case.


    -------------------------------------------------

    Tomq napisał:
    Jak to możliwe, że "BlowerState" jest zmienną strukturalną (bo po zamknięciu definicji struktury możemy deklarować jej zmienne i tak właśnie tu jest zrobione, za nawiasem "}" stoi nazwa BlowerState)
    Kod: c
    Zaloguj się, aby zobaczyć kod


    A jednocześnie jest ono strukturą (bo za pomocą nazwy BlowerState deklarujemy inne struktury):
    Kod: c
    Zaloguj się, aby zobaczyć kod


    Jak dla mnie to powinno to wyglądać tak...


    typedef sporo tu zmienia.

    typedef cokolwiek NazwaTypu; <- nazywa cokolwiek jako NazwaTypu. Ponieważ masz
    Kod: c
    Zaloguj się, aby zobaczyć kod


    to tutaj cokolwiek = struct {
    void (*handle)(Blower*);
    } czyli anonimowa struktura natomiast NazwaTypu to BlowerState. BlowerState jest więc taką anonimową strukturą.

    Poniższy kod:

    Kod: c
    Zaloguj się, aby zobaczyć kod


    To utworzenie zmiennej globalnej o typie BlowerState (czyli takiej struktury jaką widzisz w typedefie) o nazwie idleState z przypisaniem jej wartości domyślnej do jedynego pola tej struktury -> wskaźnika na funkcje handle. Zrobiłem to tak, żeby nazwać sobie możliwe stany dmuchawy. Trzeba napisać trochę więcej kodu, ale na dłuższą metę jest on czytelniejszy, no bo masz z góry zadeklarowane możliwe stany w jakich może znajdować się dmuchawa. Równie dobrze można nie robić żadnych struktur, tylko zrobić 500 zmiennych globalnych (w tym wskaźniki na funkcje ;]).

    Struktura daje też możliwość zainstalowania i kontrolowania kilku dmuchaw. Wystarczy zadeklarować dwie zmienne o typie takiej struktury i tyle. Ciekawe jak Panowie od switch - casy z globalnymi zmiennymi rozwiążą taką sytuację? Dajmy na to dla 10 dmuchaw.
  • #42
    tymon_x
    Poziom 30  
    To jest maszyna stanów kalkulatora według schematu przedstawionego tutaj: http://en.wikipedia.org/wiki/UML_state_machine#Hierarchically_nested_states.
    Poniższy przykład zawiera:
    :arrow: proste dziedziczenie znane z obiektowości. Struktura fsm_t musi przylegać na początku struktury calculatora_t. Tak samo fsm_event_t do calculator_event_t.
    :arrow: obsługa zdarzeń zewnętrznych, programowanie znane jako event-driven programming.
    :arrow: stany jako wskaźniki do funkcji, to samo rozwiązanie przedstawione przez kolegę gaskoin
    :arrow: zagnieździone maszyny stanów, maszyna s1 w stanie on kalkulatora. Ma to służyć temu, że stan on obsługuje inne zdarzenia jak OFF/CLEAR, natomiast pod maszyna stanów s1 obsługuje matematyczną funkcjonalność kalkulatora. Jest to rozdzielenie odpowiedzialności, łatwiejsza obsługa i zwiększona czytelność.
    :arrow: zdarzenia entry/exit, opcjonalna obsługa reakcji na wejście do stanu lub wyjście z aktualnego stanu. Coś jak konstruktory/destruktory, zapewniają odpowiednią inicjalizację i czyszczenie stanu.

    Warto do zdarzeń zrobić bufor cyckliczny FIFO i do niego ładować zdarzenia.

    Główny kod, kalkulator:
    Kod: c
    Zaloguj się, aby zobaczyć kod


    Prosta biblioteka FSM:
    Kod: c
    Zaloguj się, aby zobaczyć kod


    Nagłówek kalkulatora:
    Kod: c
    Zaloguj się, aby zobaczyć kod


    Maszyna stanów kalkulatora:
    Kod: c
    Zaloguj się, aby zobaczyć kod
  • #43
    Tomq
    Poziom 38  
    Cytat:
    To utworzenie zmiennej globalnej o typie BlowerState (czyli takiej struktury jaką widzisz w typedefie) o nazwie idleState z przypisaniem jej wartości domyślnej do jedynego pola tej struktury -> wskaźnika na funkcje handle.

    Myślę, że dzięki Waszej pomocy zmienne strukturalne możemy teraz zostawić w spokoju - załapałem o co chodzi.
    .................................................

    Cytat:
    Co znaczy skomunikować maszyny stanu? Pytanie jakie powinno paść to chyba raczej - jak dany stan nakarmić danymi zewnętrznymi np, temperatura ?

    Skomunikować - sprawić, żeby się komunikowały, tzn żeby wymieniały informacje. Jak wiec wprowadzać dane zewnętrzne do maszyny stanów? Ja widzę tylko dwie opcje - zmienne globalne albo globalna struktura obejmująca cały program (czy nie wychodzi więc na jedno?). W pierwszym wypadku deklarujemy zmienną globalną temperatura w zależności od niej zmieniamy stany dmuchawy, pompy, wyświetlacza, etc. W drugim wypadku cały czas operujemy na jednej strukturze, a dana funkcja zależna jest od danego pola (np dmuchawa jest zależna od pola .temperatura, a rolety od pola .nasłonecznienie)

    Jeszcze inne rozwiązanie jaki przychodzi mi do głowy to jedna funkcja coś zwraca, a druga przyjmuje to jako argument. Można by to zrobić za pomocą lokalnej zmiennej tymczasowej, np. (jak w Główny kod, kalkulator: w przykładzie Tymona)
    Kod: c
    Zaloguj się, aby zobaczyć kod

    Ale wg mnie nie wygląda to zbyt elegancko.
    .................................................

    Cytat:
    Klawiatura to nie maszyna stanu, tylko obiekt posiadający swój własny kontekst, którym jest między innymi jej stan. Nie ma sensu też wciskać bez sensu maszynę stanów gdzie popadnie.

    A co oprócz stanu jest kontekstem klawiatury? Wg mnie klawiatura jak najbardziej nadaje się na maszynę stanu (nawet gdy ją wpakujemy do ISR). Jej stany to:
    - stan "wciśnięto klawisz",
    - stan "nie mogę nic zrobić, bo czekam, aż styki przestaną drgać",
    - stan idle, czyli "czekam aż ktoś coś wciśnie".



    Cytat:
    Do klawiatury (też zależy w sumie jakiej) lepsze jest coś w stylu event source. Naciskasz klawisz -> wywołujesz event "naciśnięto klawisz detonacji urządzenia" -> łapiesz event i wołasz jego obsługę.

    Tego nie rozumiem. Kto miałby łapać event i jak? Każdy inny obiekt miałby sprawdzać, czy funkcja obsługująca klawiaturę zwróciła numer klawisza i czy ten klawisz go dotyczy?
    .................................................


    Cytat:
    Równie dobrze można nie robić żadnych struktur, tylko zrobić 500 zmiennych globalnych (w tym wskaźniki na funkcje ;]).

    Nie wiem czy moje rozwiązanie pod tym względem jest dużo gorsze niż Twój przykład. Wskaźników na funkcje musi być tyle co obiektów (dmuchawa, rolety, Uart, etc, etc). I tak samo jest ze zmiennymi strukturalnymi - ich też musi być ich tyle samo co obiektów. Przewagę struktur nad moimi wskaźnikami na funkcje jednak dostrzegam w tym, że w moim przypadku funkcja robi coś na zmiennych globalnych, a w Twoim funkcja działa "na swoim podwórku", tzn w obrębie własnej struktury.

    Co do switch-casów. Tutaj https://www.elektroda.pl/rtvforum/viewtopic.php?p=5491796#5491796 są one zewnętrzne (one wywołują określony stan), a w przypadku moich wskaźników na funkcje nie trzeba zewnętrznych switch-casów, bo stan zmienia się z wnętrza funkcji.

    "Ifa" tak czy inaczej należałoby dodać w Twoim przykładzie, bo jest on na tyle uproszczony (co jest zrozumiałe, bo jak widać nawet takiego uproszczonego przykładu nie byłem w stanie szybko ogarnąć), że maszyna automatycznie przechodzi z jednego stanu w drugi.
  • #44
    gaskoin
    Poziom 38  
    Nie przekazujesz danych do maszyny stanów tylko do stanu! Można to zrobić albo przez zmienną globalną albo przez parametr funkcji obsługującej stan (pod warunkiem, że wszystkie stany to obsługują, w przeciwnym przypadku będziemy mieli we wszystkich handlerach niepotrzebne parametry).

    Co do klawiatury to trochę się zakręciłem. Chodziło mi o to, że nie ma sensu tak kombinować. Lepiej zrobić odczyt na timerze i mieć zmienną ze stanami portu. Kodu 3 linijki + konfiguracja timera.

    Eventy - musiałbyś poczytać o eventbusie. Można zaimplementować bardzo uproszczoną wersję:
    -> wykrywasz naciśnięcie przycisku (jakkolwiek: ify, porównanie poprzedniego stanu na porcie, przerwanie, etc)
    -> "zgłaszasz event" - na uC w języku C najprościej to zrobić wywołując po prostu metodę, która jako parametr przyjmuje rodzaj eventu + jakieś ewentualne dane.
    -> W metodzie masz handlery, które obsługują konkretny typ eventu. Nie trzeba tutaj robić switch-case'a, wystarczy mapa, która mapuje typ eventu na handler.

    Tomq napisał:
    Przewagę struktur nad moimi wskaźnikami na funkcje jednak dostrzegam w tym, że w moim przypadku funkcja robi coś na zmiennych globalnych, a w Twoim funkcja działa "na swoim podwórku", tzn w obrębie własnej struktury.


    Jeśli nie chcesz uwierzyć na słowo, to jak trochę dłużej poprogramujesz, to sam dojdziesz do wniosku, że używanie i operowanie na zmiennych globalnych w cale nie jest takie fajne. Pracowałem z kilkoma kodami i czasem refactor trwał dłużej niż pisanie czegoś nowego przez zmienne globalne i nie pakowanie zmiennych tematycznie do struktur (no bo nie wiadomo co jest i po co).
    -> Kod się ciężko czyta, bo funkcje korzystają z porozrzucanych tu i ówdzie zmiennych globalnych, których wartości są tu i ówdzie pozapisywane.
    -> Jest to błędogenne i łatwo o jakieś "dziwne" stany tych zmiennych globalnych. Przy programowaniu strukturalnym pewnie nie będzie to problem, ale jak zaczniesz używać systemu operacyjnego, to nagle nie wiadomo co i gdzie Ci modyfikuje zmienne.
    -> Ciężko kod modyfikować, bo funkcje są zależne od globalnego stanu. Nie da się ich po prostu wyrzucić do innego pliku. W przypadku zmiennych lokalnych/parametrów funkcji jest to proste.

    Tomq napisał:
    "Ifa" tak czy inaczej należałoby dodać w Twoim przykładzie, bo jest on na tyle uproszczony (co jest zrozumiałe, bo jak widać nawet takiego uproszczonego przykładu nie byłem w stanie szybko ogarnąć), że maszyna automatycznie przechodzi z jednego stanu w drugi.


    No tak, ale nie służy on do sprawdzania stanu w jakim się znajdujesz (bo to jest przecież nieważne), tylko do sprawdzenia jakiegoś zewnętrznego czynnika, od którego zależy następny stan.

    Generalnie wzorzec stanu wywodzi się z języków obiektowych. Jeśli chcesz pisać czystszy i bardziej uporządkowany kod to polecam przesiadkę. Nie jest ona łatwa, programiści często używają obiektów jak struktur, ale wiele rzeczy można napisać łatwiej. Np wzorzec stanu, który gdzieś tam podałem dla języka C, jest w C++ łatwiejszy do napisania. Eventbus też jest prostszy do zrobienia w C++ (tak po prawdzie to jest trudniejszy, ale da się go zrobić ładniej i bardziej uniwersalnie).

    PS. Co do kalkulatora - to można go zrobić stosując pattern interpretter http://en.wikipedia.org/wiki/Interpreter_pattern. Przykład w javie ale na cpp można łatwo przepisać.

    Żeby nie było - co kto lubi, nie neguję niczyjego rozwiązania. Chcecie to róbcie switch casy, ale takie rozwiązania są mało elastyczne. Przy większym projekcie, w którym klient (kórym możecie być wy sami) często robi zmiany, jest to tragedia.
  • #45
    Tomq
    Poziom 38  
    Cytat:
    Można zaimplementować bardzo uproszczoną wersję:
    -> wykrywasz naciśnięcie przycisku (jakkolwiek: ify, porównanie poprzedniego stanu na porcie, przerwanie, etc)
    -> "zgłaszasz event" - na uC w języku C najprościej to zrobić wywołując po prostu metodę, która jako parametr przyjmuje rodzaj eventu + jakieś ewentualne dane.
    -> W metodzie masz handlery, które obsługują konkretny typ eventu. Nie trzeba tutaj robić switch-case'a, wystarczy mapa, która mapuje typ eventu na handler.

    Dobre:)


    Cytat:
    Jeśli nie chcesz uwierzyć na słowo, to jak trochę dłużej poprogramujesz, to sam dojdziesz do wniosku, że używanie i operowanie na zmiennych globalnych w cale nie jest takie fajne.

    Ja już to teraz wiem. Dlatego też napisałem, że to jest przewaga struktur (w sensie: wg mnie jest to ich zaletą). I w którymś ze wcześniejszych postów pytałem jak pisać kod, by się pozbyć zmiennych globalnych , ale myślę, że przedyskutowaliśmy już tyle rozwiązań, że jakoś sobie poradzę. Teraz muszę to wszystko przećwiczyć.



    Cytat:
    No tak, ale nie służy on do sprawdzania stanu w jakim się znajdujesz (bo to jest przecież nieważne), tylko do sprawdzenia jakiegoś zewnętrznego czynnika, od którego zależy następny stan.

    Czyli tu się dobrze rozumiemy:)


    Cytat:
    Generalnie wzorzec stanu wywodzi się z języków obiektowych. Jeśli chcesz pisać czystszy i bardziej uporządkowany kod to polecam przesiadkę. Nie jest ona łatwa, programiści często używają obiektów jak struktur, ale wiele rzeczy można napisać łatwiej. Np wzorzec stanu, który gdzieś tam podałem dla języka C, jest w C++ łatwiejszy do napisania. Eventbus też jest prostszy do zrobienia w C++ (tak po prawdzie to jest trudniejszy, ale da się go zrobić ładniej i bardziej uniwersalnie).

    Jak do tej pory to zajmuje się tylko 8-bitowcami, a po drugie, wydaje mi się, że muszę osiągnąć wyższy poziom zaawansowania w C, żeby myśleć o C++ (nie to, że ogólnie jest to niezbędne, po prostu wolałby się choć jednego języka nauczyć porządnie). Prawda jest też taka, że jednak próbuje działać coś w kierunku pseudoobiektowości w C.


    Cytat:
    Żeby nie było - co kto lubi, nie neguję niczyjego rozwiązania. Chcecie to róbcie switch casy, ale takie rozwiązania są mało elastyczne. Przy większym projekcie, w którym klient (kórym możecie być wy sami) często robi zmiany, jest to tragedia.

    Absolutnie nie odbieram Twoich wypowiedzi jako próby namawiania do jakiegoś jedynie słusznego rozwiązania. Sposób pisania kodu zaproponowany przez Ciebie bardzo mi się spodobał, dlatego dopytywałem o szczegóły.
    BTW, bardzo dziękuję za cierpliwość w tłumaczeniu tego wszystkiego.
  • #46
    Freddie Chopin
    Specjalista - Mikrokontrolery
    gaskoin napisał:
    Eventbus też jest prostszy do zrobienia w C++ (tak po prawdzie to jest trudniejszy, ale da się go zrobić ładniej i bardziej uniwersalnie).

    Zapodaj lepiej jakiś fajny kod (np. link) (;

    4\/3!!
  • #47
    Jado_one
    Poziom 22  
    Ja też chętnie poczytam o eventach :-)
    Ostatnio tak kombinuję, że może dobrze byłoby zastąpić dynamiczny polling (czekanie na spełnienie jakiegoś warunku w danym stanie) po prostu wywołaniem danego stanu (z "niebytu") w momencie wystąpienia warunku (czyli eventu).
    Niestety, ale "coś" co zarządza eventami i potem przekazuje sterowanie do odp. fragmentu kodu musi wiedzieć gdzie potem to sterowanie przekazać.
    A zatem przed oddaniem sterowania przy wyjściu z danego stanu trzeba "zapisać gdzieś" odpowiednie dane (może jakiś bufor FIFO ze wskaźnikami do funkcji które powinny zostać wywołane po wystąpieniu danego typu eventu).
    Druga sprawa która mnie niepokoi, to sposób w jaki procedura zarządzająca dowiaduje się o wystąpieniu danego eventu - jeśli jest to sprawdzanie, to znowu mamy polling tylko przeniesiony na inny poziom sterowania.
    Musiałoby być coś takiego, że event wywołuje procedurę zarządzającą, a ta wywołuje czekający w kolejce stan jakiejś maszyny stanów - bez sprawdzania po kolei co ją wywołało.
    Event przykładowo to mogłoby być np. zakończenie wysyłania/odbierania bajtu przez SPI.
    Zastanawiałem się nad użyciem przerwania software'owego tzn. przerwanie od SPI, po skończeniu transmisji, wywołuje owo software'owe przerwanie (ale chyba z jakimś parametrem, żeby było wiadomo co je wywołało), a tam już procedura bierze z bufora wskaźnik na odp. funkcję, ładuje gdzie trzeba i przekazuje sterowanie do pętli głównej gdzie program skacze do tej właśnie funkcji.
    Myślicie, że to miałoby szanse zadziałać? ( o ile udało mi się dobrze przekazać o co mi chodzi :-))
  • #48
    Freddie Chopin
    Specjalista - Mikrokontrolery
    Jado_one - przecież to o czym piszesz jest podstawą teorii hierarchicznych maszyn stanów - jeśli wystąpi jakieś zdarzenie, to zostaje ono przekazane do "aktualnego" stanu, który może je obsłużyć lub nie. W tym drugim przypadku zdarzenie jest propagowane "wyżej" i może być obsłużone przez któryś ze stanów nadrzędnych. Dowolne "akcje" inne niż zmiana stanu mogą być powiązane tylko z tzw. "entry" i "exit" lub z samą zmianą stanu, natomiast nie ma czegoś takiego, że jakaś akcja wywoływana jest w kółko tylko dlatego że HSM jest w danym stanie - to można osiągnąć tylko timerem i obsługą zdarzenia TIMEOUT w danym stanie, a więc po prostu zdarzeniem.

    Innymi słowy - w HSM wywołujesz "cokolwiek" tylko i wyłącznie gdy nastąpi jakiś event, z tym że czasem eventy generowane są też z samego HSM.

    4\/3!!
  • #49
    Jado_one
    Poziom 22  
    Freddie Chopin napisał:
    natomiast nie ma czegoś takiego, że jakaś akcja wywoływana jest w kółko tylko dlatego że HSM jest w danym stanie
    4\/3!!

    U mnie też nie ma "akcji wywoływanej w kółko" bo inaczej maszyna by "sama sobie szła gdzieś w maliny" - u mnie jest tylko sprawdzanie w danym stanie "w kółko" czy oczekiwany event się wydarzył czy nie (konkretnie np. czy wysłał się bajt przez SPI).
    Jeśli się wydarzy, to maszyna może "uruchomić swój stan" i pójść dalej.
    Jest to najprostszy możliwy mechanizm, który nie wymaga dodatkowych zasobów, poza samym aktem sprawdzania, który jednak zajmuje jakiś czas.
    Nie zaszkodzi trochę udoskonalić kod (wszak nam wszystkim o to chodzi ;-) ), choć widzę już, że kosztem dodatkowych zasobów pamięciowych (kolejki eventów) i dodatkowego wpisywania "sladu wywołania".
    Ale dzięki temu może wzrosnąć responsywność układu na zdarzenia, bo w tej chwili wynosi ona u mnie ok 5-6us, ale z każdą dodaną nową maszyną stanów będzie ona malała.

    Tak BTW: Teoria teorią - można wyczytywać wiele na różnych stronach Wiki/gdziekolwiek, ale najtrudniejsze jest przejście od teorii do praktyki ;-) Z tą wiedzą i umiejętnościami jakimi się aktualnie dysponuje.

    Implikacją w/w sposobu będzie (najprawdopodobniej) możliwość (tak jak to niedawno ktoś wspominał w wątku o wyświetlaczach HD44780) usypiania procesora w momencie braku zdarzenia czyli "pozbycie się pętli głównej".

    Do "szczęścia" brakuje mi tylko jeszcze sposobu zarządzania zezwoleniami na wykonywanie się kodu w sytuacji kiedy np. dwie maszyny stanów korzystają z tego samego zasobu. Do tej pory po prostu sprawdzałem czy druga maszyna jest w trakcie "wykonywania się" - jeśli tak, to pierwsza maszyna musiała czekać na dostęp. Jeśli pierwsza uzyskała dostęp, to działała, a druga musiała czekać na skończenie działania tej pierwszej, itd.....

    Takie podejście oczywiście powoduje, że programista musi pamiętać o tym, jaka maszyna z jakich zasobów korzysta i dopisywać na początku wywołania danej maszyny, sprawdzenie wszystkich tych, które mogą się jej "wciąć" w działanie.
    Jest to nieco upierdliwe ;-)

    Ale może zastosowanie w/w mechanizmu kolejkowania zdarzeń automatycznie rozwiąże tą kwestię? Hmmm...trzeba będzie to przemyśleć :-)
  • #50
    Freddie Chopin
    Specjalista - Mikrokontrolery
    Powiem Ci, że naprawdę masz dziwne pomysły. To o czym pisałem to nie jest tylko teoria - mam zaimplementowaną taką właśnie maszynę stanów (korzysta z C++ i funkcji wirtualnych, ale równie dobrze można to zrobić sposobem "obiektowym" w C - tak jak pokazał to gaskoin). Tam nie ma żadnego wchodzenia do stanu tylko po to, żeby on sobie coś sprawdził - kod jest wywoływany tylko i wyłącznie gdy pojawi się jakieś zdarzenie.

    W skrócie:

    Kod: C
    Zaloguj się, aby zobaczyć kod


    P.S. Kolejka jest tutaj tylko dla zachowania wielowątkowości, a nie do tego co opisywałeś.

    To naprawdę działa dokładnie tak jak chcesz, bez żadnych udziwnień i kombinowania - wręcz "referencyjna" implementacja idei HSM. Idea jest taka, żeby "stan" był obiektem z funkcjami wirtualnymi dla każdego możliwego zdarzenia - oczywiście te zdarzenia które nie są obsługiwane przez dany stan są "odziedziczone" ze stanów nadrzędnych i tym sposobem masz hierarchiczność. Najbardziej nadrzędny stan ma wszystkie funkcje dla każdego wydarzenia, ale one nic nie robią (czyli nie dokonują żadnej zmiany stanu HSM) - tym samym jeśli w hierarchii dane zdarzenie nie jest obsługiwane, to po prostu zostanie zignorowane.

    4\/3!!
  • #51
    Jado_one
    Poziom 22  
    Dziwne, dlaczego dziwne? ;-) Może dlatego, że zaczynałem od assemblera i ten sposób myślenia wciąż jest u mnie obecny, co przekłada się na styl programowania.
    Kiedyś gdzieś czytałem, że "kto programował w assemblerze nigdy nie nauczy się myślenia obiektowego" - może coś w tym jest :-)

    Zobacz jak to u mnie wygląda, bo mówimy o abstrakcjach.

    To jest pętla główna:
    Kod: c
    Zaloguj się, aby zobaczyć kod


    A tu w jednym ze stanów jest realizowane sprawdzenie czy można wywoływać dany stan (bo w nim będzie dostęp do fizycznego zasobu):
    Kod: c
    Zaloguj się, aby zobaczyć kod


    Może uda mi się pozbyć tego sekwencyjnego wywoływania maszyn stanów w pętli głównej na rzecz wywoływania tylko tej, która jest w danym momencie potrzebna - w momencie wystąpienia eventu - o tym pisałem wyżej.
  • #52
    Tomq
    Poziom 38  
    Obsługa eventów jest o tyle trudna, że należałoby zrobić albo jakiś większy mechanizm, który znałby wszystkie funkcje maszyny stanów i wiedziałby, która z nich powinna odpowiedzieć na dane zdarzenie, albo zaimplementować do wszystkich maszyn stanów obsługę wszystkich eventów.
    Trzecia opcja - maszyna stanów zna tylko własny event i reszte przekazuje dalej (wyżej) - tak jak pisał Freddie Chopin. Ale IMHo jeśli trzeba przerzucić kilka poziomów to trochę to zajmie.
  • #53
    Freddie Chopin
    Specjalista - Mikrokontrolery
    Tomq napisał:
    Ale IMHo jeśli trzeba przerzucić kilka poziomów to trochę to zajmie.

    Przecież dzięki funkcjom wirtualnym (albo tablicy wskaźników na funkcje - analogiczny mechanizm dla zwykłego C) nie ma żadnego znaczenia jak "nisko" lub "wysoko" jest wywoływana funkcja - wywołanie zajmuje zawsze tyle samo... Naprawdę - ilość mitów wokół C++ i bardziej abstrakcyjnych mechanizmów jest ogromna...

    Tam nie ma żadnego "ręcznego" przerzucania zdarzenia "wyżej" - w kodzie opartym o "state pattern" (czyli - w skrócie - o funkcje wirtualne) hierarchia jest "gratis" od samych mechanizmów języka.

    Wcześniej prezentowałem "wysłanie" zdarzenia do maszyny stanów, a teraz funkcja dispatch() (uprzedzam, że dla niektórych składnia może być przytłaczająca (; ):

    Kod: cpp
    Zaloguj się, aby zobaczyć kod


    "event" to wskaźnik na funkcję wirtualną (pointer-to-member) i wywoływany jest on natychmiast - bez żadnego mojego kodu ustalającego co ma się wykonać.

    4\/3!!
  • #54
    TWl
    Poziom 20  
    Freddie Chopin napisał:

    Wcześniej prezentowałem "wysłanie" zdarzenia do maszyny stanów, a teraz funkcja dispatch() (uprzedzam, że dla niektórych składnia może być przytłaczająca (; ):



    Freddie, a podzieliłbyś się może przykładowym kodem kompletnej maszyny stanów (jeśli to nie ściśle tajne/poufne)? Łatwiej byłoby zrozumieć całość niż wycinki ;)

    Czy cała klasa HSM jest template'm a STATE i ARG to jego parametry?

    Pozdr,
    TWl
  • #55
    Jado_one
    Poziom 22  
    Ja to widzę (u siebie) miej więcej tak:

    1. Maszyna stanów kończy swój stan w którym inicjuje np. wysłanie bajtu przez SPI, i jednocześnie do bufora oczekiwań na eventy (na koniec kolejki) wpisuje swój adres &, oraz numer eventu na który oczekuje (jakąś numerację trzeba przyjąć) i wychodzi z maszyny stanów przekazując sterowanie do pętli głównej lub usypiając procesor.

    2. W pętli głównej mamy procedurę wywoływaną w/g wskaźnika - jeżeli aktualnie nic się nie dzieje, jest to adres pustej pętli w której nic nie robi (idle).

    3. Po wysłaniu/odebraniu bajtu przez SPI, procedura obsługi tego przerwania wywołuje przerwanie software'owe SWI wcześniej ustawiając np. w jakiejś zmiennej nr eventu jaki jest przypisany do eventu od SPI.

    4. Obsługa przerwania SWI przeszukuje bufor oczekiwań na eventy w/g numeru eventu jaki jej został przekazany przez przerwanie SPI i po znalezieniu, pobiera adres maszyny stanów związany z tym wpisem (czyli maszyny stanów oczekującej na ten event) i wpisuje go do wskaźnika.
    Potem procedura musi jeszcze dokonać skasowania danego wpisu z bufora i przesunięcia pozostałych wpisów o 1 pozycję w górę, wypełniając w ten sposób dziurę po wykorzystanym już "rekordzie".

    Tu mamy niestety słaby punkt, bo marnujemy czas na porządkowanie, zamiast przekazać sterowanie do pętli głównej (do naszej maszyny stanów), ale z drugiej strony pozostawienie "dziury" będzie tworzyć fragmentację, a nie można jej wypełnić nowym 'rekordem", bo liczy się kolejność zgłoszenia.

    Można by ew. stworzyć "bufory tematyczne", tzn takie które trzymałyby zgłoszenia oczekiwania tylko na jeden typ eventu - wtedy pozbylibyśmy się przeszukiwania (po prostu brało by się pierwszy na liście rekord), ale tych buforów musiało by być tyle ile rodzajów eventów - cholera wie ile tego jest potrzebne ;-)
    Ale wtedy mamy najszybszą obsługę - brak wyszukiwania i brak porządkowania - po prostu pobranie adresu maszyny stanów i 'skok' do niej.

    5. Po skończeniu obsługi SWI sterowanie przekazywane jest do pętli głównej i ponownie wywoływana jest nasza procedura - ale skacze tym razem nie do adresu idle, a do 'przeładowanego' właśnie adresu naszej maszyny stanów - tej która czekała na event, który własnie został zgłoszony.

    Pozostaje jeszcze kilka kwestii do 'ogarnięcia':
    - co jeśli w krótkim czasie zostaną zgłoszone dwa eventy - czy nie nastąpi nadpisanie adresu właśnie wykonywanej procedury nowym adresem (a może utrata tego adresu, bez obsługi). Trzeba by chyba zrobić znowu kolejkę adresów procedur do kolejnego obsłużenia i po kolei je obsługiwać aż do opróżnienia kolejki.

    - co ze stanami, które nie kończą się oczekiwaniem na jakiś event, a są tylko "przygotowaniem" do następnego stanu - czasami dla wygody dzieliłem tak program - trzeba by się pozbyć takich pośrednich stanów na rzecz tych, które zawsze kończą się oczekiwaniem na event (bo inaczej te pośrednie nigdy by się nie wywoływały).
    Ale to już jest tylko kwestia uporządkowania.

    - czy jest możliwe "wcięcie się" innej maszyny stanów operującej na tym samym zasobie przez inną maszynie stanów? Np. jedna rozpoczęła transmisję do karty SD via SPI, a druga początkowo była 'napędzana' przerwaniem od I2C, ale w ostatnim swoim stanie również ma potrzebę odwołania się do SPI.
    Jeśli dopisze się do kolejki oczekiwania na eventy ze swoim adresem, to zostanie wywołana pomiędzy kolejnymi transmisjami pierwszej maszyny stanów.
    A jak wiemy podniesienie CS zeruje licznik adresów w urządzeniu obsługiwanym przez SPI. Jeżeli przesyłamy jakąś większą ilość danych z automatyczną inkrementacją wewn. licznika, to kiszka....

    I niestety jest to fatalny słaby punkt, bo jak na razie nie widzę dobrego rozwiązania.
    Maszyna stanów (ta od I2C początkowo) musiałaby wiedzieć czy może się bezpiecznie 'wciąć' ze swoim transferem przez SPI nie niszcząc wcześniej rozpoczętej już transmisji.
    Czy mam ustawić jakiś dodatkowy bit 'no entry now' ? ;-)
    Ale niestety maszyna musi się do jakiejś kolejki oczekiwania na eventy dopisać, bo przestanie być obsługiwana - wypadnie z gry.
    Procedura obsługi eventów (ta w SWI) musiałaby robić coś w rodzaju 'ignore other than current event' do momentu wyzerowania bitu 'no entry now'....(a ten byłby zerowany w ostatnim stanie pierwszej maszyny stanów, ew. w stanie kończącym 'burst transmission' przez SPI).
    Może by to zadziałało...może... Ale za bardzo mi się to nie podoba na razie....

    Może ktoś ma lepszy pomysł - o ile to co piszę jest zrozumiałe przez innych ;-)

    Edit:
    Chyba mam rozwiązanie - w przypadku transferu wielobajtowego trzeba zdefiniować event nie jako transfer pojedynczego bajtu, a całego pakietu - ta obsługa musi być zrobiona w całości w przerwaniu od SPI.
    Innymi słowy przerwanie od SPI nie zgłosi wystąpienia eventu po wysłaniu jednego bajtu, a dopiero po całym pakiecie.

    No to może, może....się uda :-) Chyba, że gdzieś jest jeszcze słaby punkt którego nie widzę.

    C++ nie znam , więc przykłady Freddiego tylko jako idea mogą mi 'podejść' - ale i to dobre... W końcu całe to myślenie zaczęło się po zdaniu "że RTOS natychmiast obsługuje zdarzenie" ;-)
  • #56
    Freddie Chopin
    Specjalista - Mikrokontrolery
    Jado_one napisał:
    Może ktoś ma lepszy pomysł - o ile to co piszę jest zrozumiałe przez innych

    Tak - użyć systemu operacyjnego, hierarchicznego automatu stanu opartego o obiekty oraz wszelkich udogodnień tego "wzorca" - funkcji entry/exit oraz "tymczasowego blokowania eventów" (event deferral - http://en.wikipedia.org/wiki/Hierarchical_state_machine#Event_deferral )... No naprawdę, ale próbujesz teraz wymyślić coś co już zostało wymyślone dawno temu, tyle że chcesz koniecznie zrobić to inaczej niż "się to robi" (;

    Jak nie lubisz RTOSów, to bardzo ciekawą opcją jest Quantum Platform (QP - sama idea, bo kod akurat nie jest specjalnie fajny moim zdaniem [wiadomo że mnie się żaden nie podoba <; ]) i będący jego częścią "system dla maszyn stanów" (QK - Quantum Kernel) - warto poczytać generalnie to co jest na stronce quantum leaps, bo informacje są bardzo interesujące. http://www.state-machine.com/resources/articles.php - seria "A crash course in UML state machines", a później inne artykuły. Tu jest też kilka ciekawych - http://www.state-machine.com/resources/appnotes.php - szczególnie ten o implementacji idei "deferred event".

    TWl napisał:
    Freddie, a podzieliłbyś się może przykładowym kodem kompletnej maszyny stanów (jeśli to nie ściśle tajne/poufne)? Łatwiej byłoby zrozumieć całość niż wycinki

    Ale chodzi Ci o kod tego template'a, czy o kod jakiejś rzeczywistej maszyny stanów która go używa? Generalnie nie mogę udostępnić całości, jednak mogę pokazać fragmenty...

    TWl napisał:
    Czy cała klasa HSM jest template'm a STATE i ARG to jego parametry?

    Z grubsza tak. Mój poziom znajomości template'ów nie jest taki jak bym chciał, więc pewnie niektóre rzeczy można by zrobić inaczej/prościej, w każdym razie działa to całkiem sprawnie.

    Kod: cpp
    Zaloguj się, aby zobaczyć kod


    W każdym razie po tym jak już to zrobiłem, natknąłem się na jeszcze bardziej kosmiczny pomysł - http://accu.org/index.php/journals/252 - nawet udało mi się to zaktualizować do C++11 i trochę poprawić, bo w oryginalnym pomyśle było kilka błędów (dotyczących postrzegania zasad UMLa) i to mógłbym udostępnić, choć to jest WIP.

    4\/3!!
  • #57
    gaskoin
    Poziom 38  
    Freddie Chopin napisał:
    gaskoin napisał:
    Eventbus też jest prostszy do zrobienia w C++ (tak po prawdzie to jest trudniejszy, ale da się go zrobić ładniej i bardziej uniwersalnie).

    Zapodaj lepiej jakiś fajny kod (np. link) (;

    4\/3!!


    Nie mam za bardzo czasu nic pisać :) https://github.com/danquist/EventBus to wygląda nawet w miarę. W cpp brakuję trochę czegoś na wzór adnotacji, po których dało by się szukać metod. Podobne cuś można uzyskać wzorcem obserwatora, ale jest bardziej wiążące. Ale coś za coś - czym bardziej odwracamy kontrolę, tym bardziej ją tracimy :)
  • #58
    Freddie Chopin
    Specjalista - Mikrokontrolery
    gaskoin napisał:
    Nie mam za bardzo czasu nic pisać :) https://github.com/danquist/EventBus to wygląda nawet w miarę.

    No wszystko byłoby spoko, tylko:
    1. Singleton
    2.
    Code:
       typedef std::list<EventRegistration*> Registrations;
    
       typedef std::unordered_map<std::type_index, std::list<EventRegistration*>*> TypeMap;

       TypeMap handlers;

    O ile std::list jeszcze przejdzie, to unordered_map zajmuje pewnie z 200kB kodu i ze 100kB RAMu <; Przesadzam oczywiście, ale akurat mapy zajmują baaaardzo dużo niestety...

    Ale dosyć ciekawe pomysły tam można podpatrzeć no i fajny interfejs (;

    4\/3!!
  • #59
    Jado_one
    Poziom 22  
    Freddie Chopin napisał:
    No naprawdę, ale próbujesz teraz wymyślić coś co już zostało wymyślone dawno temu, tyle że chcesz koniecznie zrobić to inaczej niż "się to robi" (;

    Ja tylko staram się rozwiązać pewien (na razie wirtualny) problem jaki wynikł podczas pisania programów. "Zapuszczam myślenie" i patrzę do czego w wyniku tego dochodzę. A czy ktoś to wymyślił wcześniej czy nie to już w to nie wnikam.
    Każdą rzecz można rozwiązać na 100 sposobów - ale jak widzę jest "jedynie słuszna droga"

    No dobra - nie będę zaśmiecał wątku Koledze, który go założył.....
  • #60
    Freddie Chopin
    Specjalista - Mikrokontrolery
    Jado_one napisał:
    Każdą rzecz można rozwiązać na 100 sposobów - ale jak widzę jest "jedynie słuszna droga"

    To nie chodzi o to, że to co ja proponuje jest słuszne "bo tak", a to co Ty nie jest "bo nie"... Po prostu to co opisujesz jest niesamowicie skomplikowane i tak się akurat składa, że RTOS i wątki rozwiązują chyba każdy problem o którym pisałeś... Np to że dwie maszyny stanów będą chciały używać jednocześnie jakiegoś interfejsu - w RTOSie tenże interfejs albo będzie miał kolejkę (a więc nie ma możliwości blokady poza rzadkim przypadkiem gdy kolejka się zapełni), albo użyjesz sobie mutexa i problem "jak to zrobić żeby nie było konfliktu" masz załatwiony jedną linijką - mutex.lock().

    Znam też te wszystkie przysłowia, które mówią o nieszablonowych podejściach itd., jednak osobiście do sytuacji pasujących idealnie do szablonu zastosowałbym szablon - zwłaszcza że nie znajdziesz raczej artykułów mówiących o tym, że RTOS to ślepa uliczka ewolucji i że wszyscy powinni przestać tego używać. Alternatywą dla RTOSa jest system zdarzeń, który również działa bardzo dobre i "wielkim" przykładem tego jest Qt, w którym zasadnicza większość kodu opiera się o eventy (tam zwane sygnałami), a nie wątki. Tyle że tutaj dochodzimy do tego, że sposób w jaki chcesz zrobić eventy również jest "jakiś dziwny". Poczytaj sobie o "UML state machine" (choćby w artykułach na stronie quantum leaps które podlinkowałem) - zobaczysz sam, że podejście używające hierarchicznej maszyny stanów naprawdę załatwia wiele i to w sposób elegancki oraz "pewny z definicji" - bez magicznych kombinacji. Jeśli nie znasz C++ to nie szkodzi - kod (jak i cały framework) z Quantum Leaps jest oparty o switch-case i nie ma w nim zbyt wiele z funkcji wirtualnych (właśnie w tym kodzie przerzucanie zdarzenia kilka poziomów wyżej faktycznie trochę trwa).

    Nie wiem po prostu czy mam Cię przekonywać, bo wydajesz się trochę "nieprzekonywalny" i zdeterminowany aby koniecznie spróbować wymyślić koło od nowa, tyle że w formie wieloboku (;

    4\/3!!