Stavové stroje

chytré zariadenie ako stavový stroj, návrhový vzor State, prechody medzi stavmi

Videá pre cvičenie

Motivácia

Na mnohé chytré zariadenia sa dá pozerať ako na stavové stroje. To znamená, že celé správanie je možné opísať pomocou stavového diagramu, na základe ktorého vieme povedať, že v ľubovoľnom momente sa bude zariadenie nachádzať vždy v jednom konkrétnom stave. No a dnes implementáciu nášho chytrého zariadenia upravíme tak, že z neho stavový stroj spravíme.

Jednou z výhod tohto prístupu bude, že dôjde k reorganizácii výsledného kódu, kedy správanie každého stavu izolujeme do samostatného modulu.

Ciele

  1. aplikovať návrhový vzor Stav v jazyku MicroPython

  2. rozumieť stavovým diagramom

Postup

Stav a stavový stroj

Ak by sme hľadali definíciu toho, čo je to stavový stroj, tak jedna z nich by mohla znieť takto:

Stavový stroj je výpočtový model, ktorý opisuje systém pomocou stavov a prechodov medzi nimi.

Stavový stroj sa môže vždy nachádzať len v jednom stave. Jeho správanie môžeme graficky reprezentovať pomocou diagramu stavov a v objektovom programovaní ho vieme implementovať pomocou návrhového vzoru Stav.

V tomto kroku sa pripravíme na uvedenú zmenu organizácie nášho projektu a reprezentácie nášho zariadenia pomocou stavového stroja.

Diagram tried: Návrhový vzor Stav chytrého zariadenia

Stavový diagram, ktorý bude reprezentovať náš stavový stroj, sa nachádza na nasledujúcom obrázku.

Diagram stavov: konceptuálny diagram stavov chytrého senzora

Význam jednotlivých stavov je nasledovný:

  • Init - Tento stav načíta súbor s nastaveniami. V prípade, že neexistuje, je chybný alebo bolo tlačidlo na zariadení podržané krátku dobu, prejde do stavu Configuration. Ak bolo tlačidlo podržané dlhšiu dobu, prejde do stavu Factory Reset.
  • Diagnostics - V tomto stave dôjde k overeniu funkčnosti pripojených senzorov a akčných členov.
  • Configuration - Stav, v ktorom sa zariadenie prepne do tzv. AP režimu a umožní používateľovi zariadenie nastaviť prostredníctvom webového prehliadača.
  • Factory Reset - Stav, v ktorom bude zariadenie uvedené do výrobných nastavení.
  • Operation - V tomto stave prebehne proces merania.(Neskôr ho rozdelíme na viacero samostatných stavov)
  • Sleep - Zariadenie pri prechode do tohto stavu uspíme.
  • Error - Špeciálny stav, do ktorého zariadenie prejde v prípade vzniku chyby.

Task

V koreňovom priečinku vytvorte balík s názvom states, do ktorého budeme ukladať všetky stavy.

Task

V balíku states vytvorte modul state.py a v ňom abstraktnú triedu AbstractState.

Trieda AbstractState bude reprezentovať rodičovskú triedu pre všetky stavy, ktoré vytvoríme.

Abstraktná trieda AbstractState

Trieda bude mať nasledovné metódy:

  • __init__(self, device) - initor
  • enter(self) - metóda, ktorá sa zavolá pri vchádzaní do stavu
  • exec(self) - metóda, ktorá obsahuje správanie daného stavu
  • exit(self) - metóda, ktorá sa zavolá pri vychádzaní zo stavu

Task

V balíku states vytvorte kostru každého stavu.

Každý jeden stav uložte do samostatného modulu s názvom daného stavu. Napr. pre reprezentáciu stavu Init vytvoríte modul init.py, ktorého kód bude vyzerať nasledovne:

from .state import AbstractState

class Init(AbstractState):
    def exec(self):
        print('>> Init State')

Po skončení bude váš balík states vyzerať nasledovne:

states/
├── __init__.py
├── configuration.py
├── diagnostics.py
├── error.py
├── factory_reset.py
├── init.py
├── operation.py
└── state.py

Implementáciu funkcionality jednotlivých stavov vyriešite neskôr podľa dokumentácie uvedenej v nasledujúcich krokoch. Keďže sú stavy navzájom previazané, je ťažké určiť presné poradie ich vytvorenia.

Trieda Device

V tomto kroku vytvoríme triedu Device, ktorá bude reprezentovať naše chytré zariadenie. Táto trieda bude mať len niekoľko členských premenných:

  • state - aktuálny stav, v ktorom sa zariadenie nachádza,
  • settings - používateľské nastavenia, a
  • premenné reprezentujúce jednotlivé pripojené senzory a akčné členy.

Okrem nich bude obsahovať aj metódu .run(), ktorá spustí aplikačnú slučku celého stavového stroja. To znamená, že o prechod jednotlivými stavmi sa bude starať práve táto metóda.

Trieda Device

Task

Vytvorte triedu Device podľa diagramu tried spolu s initor-om.

V initor-e vytvorte a inicializujte členské premenné na hodnotu None a nastavte aktuálny stav na Init.

Task

Vytvorte metódu .change_state(), pomocou ktorej bude možné zmeniť aktuálny stav zariadenia.

Task

Vytvorte metódu .run(), ktorá bude v nekonečnej slučke volať metódu .exec() nad aktuálnym stavom zariadenia.

Nezabudnite, že pred volaním metódy .exec() treba zavolať aj metódu .enter() a po jej zavolaní je potrebné zavolať metódu .leave().

Task

Otestujte vytvorenú implementáciu.

Pre otestovanie a spúšťanie vašej implementácie si upravte modul main.py, do ktorého vložíte nasledujúci kód:

from device import Device

if __name__ == '__main__':
    device = Device()
    device.run()

Ak ste postupovali správne, tak po spustení tohto modulu sa spustí aj vaše zariadenie.

Stav Init

Zariadenie prejde do tohto stavu po zapnutí alebo po prebudení zo spánku. Bude to teda prvý stav, do ktorého sa zariadenie dostane. Jeho hlavnou úlohou je inicializovať zariadenie a načítať používateľské nastavenia.

Z tohto stavu môže zariadenie prejsť do jedného z troch iných stavov na základe nasledujúcich podmienok:

  • Ak bolo tlačidlo držané minimálne 6s, prejde do stavu Factory Reset.
  • Ak bolo tlačidlo držané minimálne 3s, ale menej ako 6s, prejde do stavu Configuration.
  • Ak nebol nájdený súbor s používateľskými nastaveniami, prejde do stavu Configuration.
  • Ak boli používateľské nastavenia načítané nesprávne, prejde do stavu Configuration.
  • Inak prejde do stavu Diagnostics.

Pre definovanie dĺžky krátkeho a dlhého držania tlačidla použite konštanty SHORT_PRESS_DURATION a LONG_PRESS_DURATION.

Aby používateľ vedel, že už drží tlačidlo stlačené dostatočne dlho, po dosiahnutí niektorého z uvedených časových intervalov zmeňte farbu LED diódy. To znamená, že:

  • Pri štartnutí zariadenia (pri spustení stavu Init), rozsvieťte RGB LED diódu na zeleno (Color.GREEN).
  • Pri krátkom držaní tlačidla (aspoň 3s), zmeňte farbu RGB LED diódy na svetlo modrú (Color.CYAN).
  • Pri dlhom držaní tlačidla (aspoň 6s), zmeňte farbu RGB LED diódy na orandžovú (Color.ORANGE).

Task

Vytvorte stav Init podľa požiadaviek a otestujte správnosť vašej implementácie.

Jednoduchý test môže vyzerať priamo v REPL režime napr. takto:

>>> d = Device()
>>> s = Init(d)
>>> s.enter()
>>> s.exec()
>>> s.exit()

Stav Configuration

Zariadenie sa v tomto stave prepne do režimu tzv. AP režimu, kedy sa z neho stane prístupový bod s webovým rozhraním, kde môže používateľ nastaviť správanie zariadenia. Počas zotrvávania v tomto stave bude RGB LED dióda svietiť na Color.CYAN.

Po ukončení konfigurácie alebo po uplynutí času nečinnosti sa zariadenie reštartuje.

Stav Factory Reset

V prípade, že zariadenie prejde do tohto stavu, odstráni sa súbor s používateľskými nastaveniami a zariadenie sa reštartuje.

Stav Sleep

Tento stav bude reprezentovať koncový stav zariadenia, kedy sa zariadenie uspí. Pred aktiváciou spánkového režimu ešte dôjde k vypnutiu zelenej LED diódy indikujúcej prevádzku zariadenia.

Keďže však ešte nevieme zariadenie uspať, v tomto stave ukončíme vykonávanie programu zavolaním funkcie sys.exit(), čo zodpovedá vyvolaniu výnimky SystemExit:

import sys

sys.exit(0)

Stav Diagnostics

Tento stav slúži na otestovanie funkčnosti jednotlivých pripojených komponentov (senzorov a akčných členov). V tomto momente však vieme otestovať správanie len senzora DHT. Otestujte teda:

  • Či po vytvorení objektu senzora nedošlo k vyvolaniu výnimky. Ak áno, tak je senzor poškodený a nedá sa použiť.

  • Či namerané hodnoty zodpovedajú rozsahu, ktorý je udávaný výrobcom senzoru. To znamená, že pre teplotu musí platiť: \[ 0 <= teplota <= 50 \] a pre vlhkosť musí platiť \[ 20 <= vlhkosť <= 90 \].

V prípade, že dôjde k chybe niektorého z testov, ukončite funkcionalitu chytrého zariadenia prechodom do stavu Error, kde svetelnou signalizáciou upozorníte na vzniknutý problém.

Stav Operation

Úlohou tohto stavu je odmerať teplotu a vlhkosť z pripojeného senzora.

Odmerané hodnoty uložte do súboru, ktorého umiestnenie uložte do konfiguračnej premennej MEASUREMENTS_FILE. Namerané údaje udržiavajte ako zoznam meraní - či už vo forme CSV súboru alebo ako zoznam zoznamov meraní vo formáte JSON.

Stav Error

Kód chyby uložte do osobitnej členskej premennej triedy Device. Na základe uvedenej chyby použite LED diódu na notifikáciu používateľa, napr. počtom jej bliknutí.

Po notifikácii prejdite do stavu Sleep.

Pred odchodom z cvičenia…

Ďalšie úlohy

  1. Do abstraktnej triedy AbstractState pridajte členskú premennú s názvom .name. Táto premenná bude obsahovať reťazcovú reprezentáciu názvu daného stavu. Tú môžete použiť pri logovaní, keď do stavu vojdete alebo z neho odídete.

  2. V stave Init pri držaní tlačidla sa pokúste upraviť kód tak, aby RGB LED dióda nesvietila, ale buď pulzovala alebo blikala až do momentu, keď sa tlačidlo nepustí.

Ďalšie zdroje

  1. State in Python

  2. MicroPython: Controlling NeoPixels

  3. MicroPython: Quick reference for the RP2: NeoPixel and APA106 driver

  4. Random Nerd Tutorials: MicroPython: WS2812B Addressable RGB LEDs with ESP32 and ESP8266 - This tutorial shows how to control WS2812B addressable RGB LEDs (neopixels) with the ESP32 and ESP8266 using MicroPython.