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
aplikovať návrhový vzor
Stav
v jazyku MicroPythonrozumieť 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.

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

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 stavuConfiguration
. Ak bolo tlačidlo podržané dlhšiu dobu, prejde do stavuFactory 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.

Upozornenie
Pre vytvorenie abstraktnej triedy viete v štandardnej knižnica jazyka
Python použiť balík abc
. Ten však v jazyku
MicroPython nenájdete. Takže triedu AbstractState
v tomto prípade implementujte ako normálnu triedu.
Trieda bude mať nasledovné metódy:
__init__(self, device)
- initorenter(self)
- metóda, ktorá sa zavolá pri vchádzaní do stavuexec(self)
- metóda, ktorá obsahuje správanie daného stavuexit(self)
- metóda, ktorá sa zavolá pri vychádzaní zo stavu
Poznámka
Metódy enter()
a exit()
môžete použiť napr.
na logovanie, rozsvietene/zhasnutie LED diódy, a pod. Keďže nie sú nutné
pre vykonanie stavu, v abstraktnej triede ich vytvorte prázdne.
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.
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.
Poznámka
Nezabúdajte, že súbor main.py
sa bude spúšťať
automaticky pri štarte zariadenia. To môže viesť ku problémom ako je
napr. zacyklenie. Pre testovanie je teda bezpečnejšie urobiť si
samostatný modul, napr. run.py
a spúšťať svoj kód v
ňom.
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.
Poznámka
Webový portál zatiaľ nevieme vytvoriť. Implementáciu stavu preto nechajte prázdnu a zabezpečte len správny prechod týmto stavom.
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.
Poznámka
Odstránením nastavení dosiahneme, že sa pri najbližšom štarte zariadenia dostaneme do konfiguračného režimu, kde si používateľ bude musieť nastavenia vytvoriť sám.
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
0) sys.exit(
Upozornenie
Neošetrením
výnimky SystemExit
dôjde k vyvolaniu mäkkého
resetu. Preto ju ošetrite v metóde .run()
triedy
Device
. V opačnom prípade sa vaše zariadenie po prechode do
tohto stavu reštartuje. A reštartuje sa zakaždým, keď tento stav
dosiahne, čím zariadenie uviazne v nekonečnej slučke.
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.
Poznámka
Konverziou teploty na veličinu podľa konfigurácie sa v tomto kroku nezaoberajte. Pokiaľ sú merania trvalo uložené na disku, udržiavajte ich v normalizovanom formáte. Prevod na požadovanú jednotku urobíte až vtedy, keď budete namerané údaje odosielať do internetu.
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…
Upozornenie
Ak máte dosku Raspberry Pi Pico 2 WH požičanú od cvičiaceho, tak pred odchodom z cvičenia si prosím:
- Uložte si vytvorený kód, ktorý ste na cvičení urobili.
- Zmažte obsah Flash pamäte mikrokontroléra nahratím fimrvéru flash_nuke.uf2. Pozor! Mikrokontrolér neodpájajte od počítača, pokiaľ sa vo vašom počítači znova neobjaví ako USB zariadenie.
- Nahrajte na mikrokontrolér najnovší firmvér.
Ďalšie úlohy
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.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
MicroPython: Controlling NeoPixels
MicroPython: Quick reference for the RP2: NeoPixel and APA106 driver
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.