upyshell2

ESP32 OctopusLAB shell 2

OctopusLAB (komunitní projekt české společnosti Octopus engine s.r.o.) již několik let vyvíjí hardware a software moduly pro rapid prototyping i výuku. Jedním z nejnovějších a překvapivě použitelných z nástrojů je i uPyShell, který je součástí celého balíčku open-source zdrojových kódů:
https://github.com/octopusengine/octopuslab/tree/master/esp32-micropython

Aktuální verze Micropythonu: 1-12-258

Pokud se vám podle popisu a odkazů z minulého dílu podařilo na Vašem ESP zprovoznit Micropython (v terminálu připojeného ESP32 vidíte >>> a kurzor – zelený čtverec úplně na konci) a následně jste zvládli i nahrát celý systém octopus, máte tu nejméně zábavnou a přitom poměrně náročnou část za sebou.

Jemné naladění systému

Systém octopus pro ESP-Micropython počítá s různými hw-moduly, na kterých mohou být připojeny rozličné periférie – senzory, displeje, a podobně. Aby nám korektně vše fungovalo, je vhodné občas provést pár dílčích nastavení, na která vás vždy upozorníme. Koncept jsme se snažili zjednodušit natolik, že máme základní moduly v knihovnách obsahujících třídy a metody (naznačili jsme už v minulých dílech o Micropythonu)

Programy s některými knihovnami mohou vypadat následovně:

from util.led import Led
led = Led(2)  # GPIO 2 na RobotBoard pro led 
led.blink     # blikne vestavěná ledka 


from util.rgb import Rgb
ws = Rgb(15)   # GPIO 15 na RobotBoard pro WS led  
ws.color(RED)  # červeně se rozsvítí RGB ws Led 

 

from util.servo import Servo
s1 = Servo(33)    # GPIO 33 - PWM pin pro servo1
s1.setangle(30)   # servo 1 se natočí na 30 st. 

....

Upozornění na odlišnosti zde v článku používané syntaxe:
>>> setup()
(>>> znamená spuštění v Micropythonu)
$ run test.py
($ znamená spuštění z prostředí shellu)

var = 123
(bez ničeho, je použito buď v programu, nebo zjednodušeně z prostředí Micropythonu >>>)


Pokud píšeme vlastní programy (což se bude týkat i většiny našich ukázek v /examples – adresáš v ESP), základ nastavení bývá zahrnut v nich, ale testovací a prototypovací systém umožní mít konkrétní nastavení i někde uloženo – a k tomu slouží konfigurační soubor config/ios.json – input output setup (nastavení vstupů a výstupů).
Nastavení se provádí v základním Micropythonu, kde akci vyvoláme pomocí metody setup:
>>> setup()
V tomto díle nám postačí ukázat si to na nastavení vestavěné Led diody.
(Zde vysvětlujeme pouze možnosti a princip – můžete klidně přeskočit až k dalšímu bodu auto-shell.)

Volba „desky“ (deskou rozumíme jeden z HW modulů pro ESP)
V prvním kroku nejdříve napíšeme ds (device setting), pro naše ukázky budeme používat: 5: oLAB RobotBoard1 | esp32
(ale hlavně se jedná o ESP32 modul DoIt – 2×15 pinů)
takže zvolte číslo 5.
Pro jiné desky nebo moduly se mění nastavení PINOUT. O tom se zmíníme ještě trochu podrobněji.

Ukázkové programy
Můžeme si z cloudu také stáhnout ukázkové programy (budou pak v adresáři /examples)
Postupně zvolte:
cw Wifi by se měla připojit, nastaveno už máme od minula)
sde system download examples stáhne opět několik desítek ukázkových souborů

Nastavení vstupů a výstupů
Následně zvolíme u select:
ios (I/O setting submenu)

zde stačí vybrat 1 (led)
a pak hodnotu 1 (enable)
tím máme Led nastavenu

Pro práci v shellu jsme přeprogramovali systém tak, že po CTRL-C se přeruší pouze běh programu ve vlákně spouštěném shellem – a vy zůstáváte v shellu! Což je skvělé. Zkusíme si:

V shellu napíšeme pro spuštění programu:
$ run examples/blink.led
a by měla blikat Ledka. Běh programu se přeruší CTRL+C, program blink se zastaví, ledka přestane blikat, ale zůstáváme pořád v shellu. (viz. poslední tři řádky na obrázku)

V tomto díle dominantně použijeme tradiční „projekt“ blikání ledkou. Je to takový „hello-word“ – základní a nejjednodušší případ, který se dá obvykle aplikovat na většinu další složitějších úloh. Takže když někde uvidíte slovní spojení „blikání ledky“, nemusíte hned nasazovat posměšný úšklebek. Představit si pod tím lze „můj program“ nebo třeba konkrétní „ovládání robotického vozítka“.

Odbočka:
Jak docílíme toho, aby se blikání Ledky samo spouštělo hned po startu? Program main.py v kořenovém adresáři, je po boot.py přesně ten, který se po startu spustí. Takže si můžeme v shellu jednoduše blikání zkopírovat:

$ cp examples/blink.py main.py

Tím jsem docílili požadovaného, ale pozor, přišli jsme o původní main.py.
Ten jsme si tedy před tím mohli/měli nějak zazálohovat, například:

$ cp main.py backup.py
Ale zdůrazňuji, před tím! Protože BACKUP FIRST!

Jak vypadá kód blink.py? V shellu zjistíme snadno:
$ cat examples/blink.py

Povšimněte si použití modulu pinout, který se inicializuje podle dříve nastavené desky (v našem příkladu „dammy“ ROBOTboard)
Ten nám ulehčí práci tím, že nemusím zjišťovat, na kterém že to pinu je vestavěná Ledka, kterou nazýváme BUILT_IN_LED. Když to máme správně nastaveno, vše funguje skvěle.

from util.led import Led
from util.pinout import set_pinout
pinout = set_pinout()           # set board pinout
led = Led(pinout.BUILT_IN_LED)  # BUILT_IN_LED = 2 (GPIO 2)

Koukněte, kde se dá celý ukázkový soubor také dohledat:
https://github.com/octopusengine/octopuslab/blob/master/esp32-micropython/examples/blink.py
Všechny zdrojové kódy modulů a knihoven, stejně tak i všechny ukázky, se snažíme mít dostupné na githubu. Naučme se do něj dívat – a ještě lépe, používat ho. Reportujme BUGy. Diskutujme nad ISSUES. A případně i sami pomocí PULL-REQUESTu přispějme svým COMMITEM.


Shell dostupný po startu?

Dalším doporučeným krokem je nastavní ESP tak, aby byl hned po restartu dostupný modul util.shell pomocí příkazu shell(). Také tento postup není nezbytný, ale pak je nutné vždy knihovnu importovat: from util.shell import shell, a my si práci chceme ulehčit. (Při vývoji po pátém opisování stejného řádku velká část programátorů řeší, jak se této zbytečné činnosti vyhnout.)

Využijeme našeho dalšího modulu Config, který do adresáře config ukládá soubory typu json, a máme tam vytvořeno pár nástrojů, které nám opět práci velmi ulehčí. Přikročme k vytvoření (nebo editaci) souboru config/boot.json, který mimo jiné definuje, co se děje při bootování (zavádění systému po restaru).

>>> from config import Config # import knihovny
>>> cfg = Config("boot")      # instance pro config/boot.json
>>> cfg.set("import_shell",1) # boot.py testuje import_shell
>>> cfg.save()                # uložení konfigurace

Nyní po „tvrdém“ restartu (vypnout/zapnout nebo hw-tlačítko EN na modulu DoIt) můžeme hned napsat: >>>shell()
Proč to není už přípmo přednastaveno? Ne vždy uživatel chce mít pohodlně nastaveno úplně všechno – na to nemáme dost RAM paměti a tou se musí velmi šetřit. Takže dílčí nastavení je vhodné operativně modifikovat, podle právě laděného projektu. Takže vás takto učíme i vytvořit si vlastní config – jak se to používá je vidět v boot.py:
https://github.com/octopusengine/octopuslab/blob/master/esp32-micropython/boot.py
A princip použití je zase velmi snadný:

 from config import Config
 autostart = Config("boot")  # /config/boot.json
 if autostart.get("import_shell"): 
     ... akce

Spuštění s parametrem

Někdy potřebujeme předat programu nějaký parametr, například jak rychle chceme, aby Ledka blikala. Spouštěný program na to musí být napsán, vstupní data čte z dostupného pole předaných parametrů _ARGS[]

Ukázka je zde:
https://github.com/octopusengine/octopuslab/blob/master/esp32-micropython/examples/param/blink_param.py

Pokud jste si stáhnuli ukázky /examples (sekvencí: >>>setup() | cw |sde)
můžete si zkusit spuštění v shellu:
$ run examples/param/blink_param.led 300
kde hodnota 300 na konci znamená, že led bude blikat (svítit, nesvítit) s prodlevou 300ms, to je zhruba 3x za vteřinu, oproti defaultnímu nastavení 1000ms (1s)


Sleep nebo timer?

Program blink i blink_param je zatím napsán tak, jak se v klasických ukázkách uvádí. V nekonečné smyčce while True čekáme nějakou dobu pomocí sleep a pak změníme hodnotu na výstupním pinu. U jednoduchých projektů se to tak dá dělat až do doby, kdy nám začne vadit, že kontroler nestíhá vlastně nic dalšího: změřit hodnotu, poslat jí do databáze, číst data z bluetooth, ovládat motory na základě příkazů, psát stavové informace na displeji… tam se příkaz sleep() delší než 1 ms v podstatě nedá použít.

while True:
    led.blink()

protože metoda blink() je napsána jako blokující, 
v našem případě je to stejné jako: 

while True:
    led.value(1)  # led_on
    sleep(1)
    led.value(0)  # led_off
    sleep(1) 
 
 

Na obrázku je vidět, kdy je procesor (myšlena hlavní řídící část mikrokontroléru) blokován (červeně) a kdy má čas dělat i něco jiného (zeleně). V případě samotného blikání ledky pomocí sleep mu necháváme jen kratičké úseky u změny stavu (zelená kolečka ne červené lince). Při použití časovače je to naopak. Časovač běží na pozadí, procesor může dělat cokoli jiného (zelená linka) a od toho je občas vyrušen požadavkem, aby změnil stav na pinu (červené kolečko)

Takže vidíme, že když chceme blikání ledkou použít jen jako signalizace stavu nebo běhu složitějšího programu, lepší by bylo použít timer (časovač), který nechá procesor v klidu až do doby, než je něco potřeba udělat.

...
from machine import Timer
tim1 = Timer(0) # instance objektu Timer

def timerAction(): 
   led.toggle() 
   # metoda toggle (přepnutí stavu) definována v třídě Led  

tim1.init(period=1000,mode=Timer.PERIODIC,callback=lambda t:timerAction())

while True:
   # another code
  

Program, který je napsán jako samostatné vlákno

Micropython nám ale nabízí (sice v omezené betaverzi) i pokročilejší modul _thread, který lze použít následujícím poměrně triviálním způsobem:

import _thread 
def runThread1():
    while True: led.blink()

_thread.start_new_thread(runThread1, ()) 

Celý kód je opět na githubu:
https://github.com/octopusengine/octopuslab/blob/master/esp32-micropython/examples/thread_blink.py

Tento případ zde uvádíme pouze pro doplnění uceleného přehledu, jak se dá napsat samostatný program. Pro potřeby vývoje a testování spouštíme programy napsané „normálně“ ve vlákně pomocí shellu, podle následující ukázky. A hlavní poznatek je, že i když je blink.py napsán diletantsky blokující (se sleep) můžeme vedle něs spustit jiný paralelní proces, který dělá něco úplně jiného.


Program spuštěný ve vlákně

Náš shell se snažíme tvořit po vzoru Linuxu, kde se proces také dá spustit s uvedením „&“ na konci příkazové řádky.

$ run examples/blink.led &

Takže blokující blikání Ledky je nyní spuštěno, jako samostatný proces ve vlastním vlákně, a další procesy běží „bez blokování“. Samozřejmě brzy narazíme na možnosti HW, jelikož ESP nám dává k dispozici pouze jedno jádro a tak více procesů jede v jakémsi multitaskingu (což nás ale jako uživatele nemusí do detailu zajímat).

Spuštění blikání pomocí run * &
Povšimněte si, že po zavolání top, vidíme běžící proces (fialově) a i když ledka pořád bliká, my můžeme dále pracovat na dalších subprocesech.
Jeden z testovacích shell příkazů je i sleep s parametrem čas [s], zkuste si spustit několikrát po sobě:
$ sleep 300 &
$ sleep 300 &
$ sleep 300 &
a pak zavolat
$ top
ano, funguje!


Proč potřebujeme spouštět samostatně paralelní procesy?

Ve stručnosti si to shrneme:
snažíme se psát SW takzvaně „neblokující„. To se dá realizovat pomocí přerušení, timerů a podobně, což je už i u trochu větších programů docela složité zvládnout.
Už i nejjednodužího blikání, kde máme použito sleep(1) – „počkej jednu vteřinu“ – je zpravidla procesor úplně zablokován a neprovádí nic dalšího (kromě obsluhy přerušení s vyšší prioritou) což znamená „blokující„.
Náš uPyShell to řeší tak, že máme jakýsi „wrapper“ (obálku) s instancí vlákna, ve kterém se dynamicky spouští program.
Dále máme v shellu napsán i elegantní modul pubsub (publish and subscribe), pomocí kterého si můžeme předávat parametry mezi jednotlivými procesy samostatně běžícími ve vláknech – a to už je jiná práce! Ale o tom podrobněji až v nějakém dalším díle. (Pokud bude zájem).