ESP32 OctopusLAB shell 2
- April 03 2020
- OctopusLab
- 1505x Přečteno
První díl si můžete přečíst na https://chiptron.cz/articles.php?article_id=255
Pokud se vám podle popisu a odkazů z minulého dílu podařilo na svých ESP zprovoznit Micropython (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.
Aktuální verze Micropythonu: v1.12-258 (v době vydání článku už v1.12-328 s novým asyncio)
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é periferie – 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.set_degree(30) # servo 1 se natočí na 30 stupňů
...
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í uPyShellu)
var = 123
(bez ničeho, je použito buď v souboru 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 v adresáři v ESP32-Micropython na našem githubu, základ nastavení bývá zahrnut v nich. Nicméně testovací a prototypovací systém umožňuje mít konkrétní nastavení i někde uloženo a k tomu slouží v kontroléru nahraný konfigurační soubor config/ios.json – input output setup, tj. nastavení vstupů a výstupů.
Nastavení se provádí v základním Micropythonu (tam jsme skončili v minulém článku), 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.)
Metoda setup() nám spustí interaktivní menu pro nastavení našeho ESP32. Jako první nastavíme OctopusLab desku, pak stáhneme sadu příkladu a nastavíme používané I/O.
Volba „desky“
Deskou rozumíme jeden z HW modulů pro ESP - tedy jedna z prototypovacích desek OctopusLab.
V prvním kroku nejdříve napíšeme ds (device setting), pro naše ukázky budeme používat volbu 5:
oLAB RobotBoard1 | esp32, takže zvolte číslo 5.
(Ale hlavně se jedná o ESP32 modul DoIt – 2×15 pinů.)
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 našeho serveru také stáhnout ukázkové programy (budou pak v adresáři /examples).
Postupně zvolte z menu setup:
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ů
Pro zpřístupnění periferií a senzorů použijeme I/O setting submenu, volíme tedy ios.
zde stačí vybrat 1 (led)
a pak hodnotu 1 (enable)
tím máme led nastavenu
(v ukázce na obrazovce je nastavena i barevná RGB - druhý řádek [ws])
Pro práci v shellu jsme odchytili přerušení uživatelem tak, že po CTRL+C se přeruší pouze běh aktuálního programu (běží v samostatném vlákně), takže vy zůstáváte stále v shellu! Což je skvělé. Zkusíme si:
V shellu napíšeme pro spuštění programu:
$ run examples/blink.py
a měla by 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-world“ – základní a nejjednodušší případ, který se dá obvykle aplikovat na většinu dalších 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 ze souboru s příkladem do hlavního skriptu:
Nejdříve si musíme zálohovat, je to dobrým zvykem, protože BACKUP FIRST!
$ cp main.py main_bck.py
Vlastní zkopírování příkladu blink.py do hlavního programu main.py.
$ cp examples/blink.py main.py
Tím jsme docílili požadovaného automatického spuštění blikání po startu.
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 „dummy“ ROBOTboard).
Ten nám ulehčí práci tím, že nemusíme 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 na github, kde se dá celý ukázkový soubor dohledat.
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 nastavení 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 pomocí 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) bootovací konfigurace, která mimo jiné definuje, co se děje při bootování (zavádění systému po restartu). Nástroj Config nám jej uloží souboru config/boot.json.
>>> 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římo 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 jsme si ukázali, jak si vytvořit vlastní config – použití je vidět v 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.
Pokud jste si stáhli ukázky /examples (sekvencí: >>> setup() | cw | sde), můžete si zkusit spuštění v shellu:
$ run examples/param/blink_param.py 300
kde hodnota 300 na konci znamená, že led bude blikat (svítit, nesvítit) s prodlevou 300 ms, to je zhruba 3x rychleji oproti defaultnímu nastavení prodlevy na 1000 ms (1 s).
Program spuštěný ve vlákně
Náš shell se snažíme tvořit po vzoru Linuxu, kde se proces také dá spustit na pozadí s uvedením „&“ na konci příkazu.
$ run examples/blink.py &
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 HW limity, 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 examples/blink.py &
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!
A hlavní poznatek je, že i když je blink.py napsán diletantsky - blokující (se sleep), můžeme vedle něj spustit jiný paralelní proces, který dělá něco úplně jiného.
Multitasking
Proč potřebujeme spouštět samostatně paralelní procesy?
Ve stručnosti: 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 u 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í“.
“Sleep je jako když člověk čeká na dodávku pizzy tak, že stojí u dveří a každou minutu otevře a podívá se, jestli pizza už dorazila, zatímco přerušení je když vám ten donašeč zavolá, že je s pizzou přede dveřmi.”
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.
Sleep
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 č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, než 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.
Naše metoda blink() se používá v programu takto:
while True:
led.blink()
Uvnitř je metoda blink() je napsána jako blokující, lze ji tedy rozepsat 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 na č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 signalizaci 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.
Timer
Časovač (timer) vyvolává IRQ přerušení v definovaném intervalu, to je pak obslouženo metodou, která se předá při inicializaci timeru.
from machine import Timer
tim1 = Timer(0) # instance objektu Timer
def timerAction():
led.toggle()
# metoda toggle (prepnutí stavu) definovana v tride Led
tim1.init(period=1000,mode=Timer.PERIODIC,callback=lambda t:timerAction())
while True:
# another code
Thread
Program, může být napsán jako samostatné vlákno. Micropython nám ale nabízí pokročilejší modul _thread (není stable, proto to podtržítko na začátku). Ten 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.
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ě“ (blokující) ve vlákně pomocí shellu - & na konci příkazu.
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.)
Async
Poslední možností je skvělá třída asyncio, která je vestavěná od Python 3.7 a nově (03/2020) je dostupná v MicroPythonu, podrobnější popis by byl na samostatný seriál článků - pokud vás zajímá základ (na našem oblíbeném příkladu blikání ledkou) prohlédněte si ukázku blink_async.py.
První díl si můžete přečíst na https://chiptron.cz/articles.php?article_id=255