XBMC-Kodi.cz

Úplná verze: Command Macro
Prohlížíte si holou variantu vašeho obsahu. Prohlédněte si plnou verzi s příslušným formátováním.
[attachment=11815]Doplněk Command Macro, id script.macro je další z řady doplňků a řešení určených pro Kodi, které jsem zařadil do skupiny, kterou prezentuji jako Easy TOOLS.

Historie a návaznosti

Doplněk navazuje na řadu diskusí týkající se mapování tlačítek dálkových ovladačů, klávesnic a jiných I/O zařízení sloužících k ovládání Kodi, zejména pak případů, kdy je třeba stiskem jednoho tlačítka vykonat postupně více interních příkazů Kodi. Jistě, některé dálkové ovladače tuto funkci nabízejí (Logitech Harmony, Skip a celá řada dalších), ale ne vždy je takové zařízení k dispozici. Taková potřeba není nová, v minulosti již vzniko více řešení, které takovou funkci nabízeli a dají se i dnes ještě na internetu či v různých repozitářích najít. Jedno řešení je dokonce i z díny známého českého autora Romana Smolky a dá se ještě na Github najít. Já taková řešení v průběhu času používal také, vždycky šlo o mou vlastní produkci přizpůsobenou mým konkrétním potřebám, takže, když mě jeden z uživatelů požádal, zda bych něco takového nevytvořil i pro něj, sáhl jsme do vlastního šuplíku a jedno z mnou dříve používaných řešení jen mírně upravil do zveřejnitelné podoby.

Koncepce řešení

Koncepce řešení je velmi jednoduchá. Funkci vykonávání souboru příkazů/maker, zajišťuje jednoduchý doplněk typu script. Makra, která vykonává, se definují v rámci volání tohoto scriptu. Pro volání scriptu se v této základní verzi používá Kodi builtin příkaz RunScript(), jeho parametry, kromě identifikace vlastního scriptu, jsou všechny příkazy, které chceme po zavolání scriptu vykonat. Nejlepší to bude vysvětlit na dále uvedeném příkladu, kvůli kterému vlastně chtěl uživatel takový script mít k dispozici.

Požadavkem uživatele bylo, aby se před uspáním Kodi (resp. boxu, na kterém Kodi běží) zastavilo případné běžící přehrávání a teprve potom, ideálně po nějaké prodlevě, se vyslal samotný příkaz k uspání celého boxu. A to všechno by mělo fungovat tak, aby se taková sekvence dala spustit stiskem tlačítka POWER na dálkovém ovladači. Dosud uživatel používal mapování tlačítka POWER, které je namapované na Kodi builtin příkaz Shutdown, která jak známo vykonává příkaz definovaný v nastavení Kodi, tedy Nastavení > Systém > Úspora energie v položce Funkce vypínání, tedy buď Uspat nebo Vypnout. Podobně je možné box uspat i přímo, použitím builtin příkadu Suspend.
 
Principem použití script.macro je, že se v uživatelských parametrech mapování přemapuje tlačítko POWER a původně nastavená funkce se nahradí volnání scriptu script.macro s parametry odpovídající požadovanému chování při stisku tlačítka POWER. Nejlépe to asi vysvětlím právě na tom požadavku zmíněného uživatele, který chtěl, aby se po stisku POWER provedlo:
  1. Zastavení případného přehrávání (video, hudba, TV, apod.).
  2. Nějaká kratší prodleva, např. 5 vteřin, aby se Kodi po skončení přehrávání dostalo do nějakého "klidného stavu". Je to vhodné (a doporučuje se to) zejména v případech, pokud se při přehrávání používá AFR.
  3. Vyslání finálního povelu k uspání či vypnutí.
Původní (default) chování je zajištěno mapováním tlačítka POWER nějak takto (jde o fragment mapovacího souboru):
 
Kód:
<keymap>
    <global>
        <keyboard>
            <power>Shutdown()</power>
        </keyboard>
    </global>
</keymap>

nebo takto:
 
Kód:
<keymap>
    <global>
        <keyboard>
            <power>Suspend()</power>
        </keyboard>
    </global>
</keymap>

V případě, budeme-li chtít zajistit výše uvedené chování podle požadavků uživatele, může při použítí script.macro mapovací soubor vypadat takto:
 
Kód:
<keymap>
    <global>
        <keyboard>
            <power>RunScript(script.macro,Action(Stop),@Delay(5),Shutdown())</power>
        </keyboard>
    </global>
</keymap>

nebo takto:
 
Kód:
<keymap>
    <global>
        <keyboard>
            <power>RunScript(script.macro,Action(Stop),@Delay(5),Suspend())</power>
        </keyboard>
    </global>
</keymap>

Parametry volání script.macro

Myslím, že parametry volání jsou celkem jasné. Ale pro jistotu znova a pěkně popořádku:
  • RunScript() - builtin příkaz Kodi, kterým se spouští doplněk typu script
  • script.macro - první parametr ve volání příkazu, kterým je vždy id doplňku, v našem případe tedy script.macro
  • Action(Stop),(5),Suspend() - následující příkazy (může jich být libovolně mnoho), které chceme, aby se spuštěním scriptu postupně vykonaly, kde:
    • Action(Stop) - builtin příkaz Kodi, který zastaví přehrávání
    • (5) - interní příkaz doplňku script.macro, který vloží do sekvence vykonávaných příkazů prodlevu danou hodnotou svého danou parametru, v tomto případě je to tedy 5 vteřin. Parametr je typu float, může to tedy být kladné reálné číslo větší než 0.
    • Shutdown() nebo Suspend() - zajistí, že Kodi se uvede do stavu podle nastavení Kodi - v případě Shutdown() nebo se rovnou uspí - v případě Suspend().
To, zda je daný parameter volání builtin příkazem Kodi nebo interním příkazem scriptu odlišuje znak @ na prvním místě.

Jaké příkazy můžeme na místě parametrů volání použít?

Jakýkoliv builtin příkaz Kodi, viz List of built-in functions a některý z aktuálně dostupných interních příkazů scriptu. V současné chvíli jsou k dispozici následujcíí interní příkazy:
  • (<time>) - s jedním parametrem <time>, kterým je čas čekání (prodleva) ve vteřinách, parametr je ve tvaru celé nebo reálné číslo >0, např. tedy 0.5, 1, 1.2, 5, ... Defaultní hodnota parametru, tedy pokud parametr chybí, je 0.5.
  • NEW (<time>) - s jedním parametrem <time>, kterým je čas čekání (prodleva) ve vteřinách, parametr je ve tvaru celé nebo reálné číslo >0, např. tedy 0.5, 1, 1.2, 5, ... Defaultní hodnota parametru, tedy pokud parametr chybí, je 0.5. Příkaz je kombinací builtin příkazu Action(Stop) a interního příkazu (<time>). V případě, pokud se v době spuštění scriptu něco přehrává, provede script příkaz k ukončení přehrávání a pak čeká zadaný čas. Pokud se ale nic nepřehrává, tak script nečeká.
Jaké kontroly jsou ve skriptu prováděny
  • Script zásadně nekontroluje syntaxi Kodi builtin příkazů. Pokud patametr neobsahuje vedoucí znak @, posílá script celý řetězec do Kodi prostřednictvím knihovní metody xbmc.executebuiltin().
  • V případě, pokud je parametr uvozen znakem @, považuje to script za interní příkaz a kontroluje jeho syntaxi a jeho vlastní parametry. Syntaxe je dána jednoduchou stuturou Command(parameter1, parameter2, ... parameterN). V rámci interního popisu syntaxe každého interního příkazu je definován počet a typy parametrů (integer, float, string, boolean). Pokud typ skutečného parametru neodpovídá typu definovanému v interním popisu syntaxe, jde o chybu a interní příkaz nebude vykonán a chyba se zapíše do logu Kodi. Podobné je to i v případě, pokud bude v paramtrech volání script.macro použito neznámý název interního příkazu.
  • Název interních příkazů funkcí je tzv. case insensitive, tzn. že můžete napsa , či jakkoliv jinak, např.  .
Pozn. Přiznám se, že funkci použití defaultní hodnoty jsem zatím dostatečně netetsoval. Dekódování syntaxe je ale napsané tak, že by měly projít všechny tyto varianty interního příkazu:
  •  - bude použita defaultní hodnota parametru, která je pro tento příkaz 0.5
  • () - bude použita defaultní hodnota parametru, která je pro tento příkaz 0.5
  • (0.5)
Omezení, chyby, To-Do a případné další infromace
  • Nezkoušejte zadávat do parametru hodnotu 0. Pokud to uděláte, bude script čekat neomezeně dlouho a vysvobodí vás pouze jeho Zakázání/Povolení nebo restart Kodi.
  • O žádných chybách nevím, budu ale rád, pokud mi je nahlásíte, pokud k nim dojde.
  • Uvažoval o doplnění interních funkcí, které by uživatelům dovolily zadávat nějaký vlastní podmínky. Například čekej do ukončení nebo zahájení nějaké akce a pak pokračuj ve vykonávání makra. Ale tohle je při možnostech pouze lineárního zápisu docela složité, spíše půjdu cestou, že pokud se nějaký takový požadavek vyskytne, já ho s pomocí knihovních funkcí Kodi zakóduji do python a nadefinuji pro něj nový interní příkaz s odpovídajícími parametry.
  • Protože parametrem volání může být interní příkaz, je možné ve volání scriptu zavolat i samotný script.macro s vlastními (i jinými) parametry. Vytvoří se nová instance, na té původní nezávislá. EDIT Toto bohužel tak úplně neplatí. Ona se sice nová instance vytvoří, ale ta stará poběží dál bez ohledu na stav té nové. Není to tedy tak, jak by člověk předpokládal, že se při vytvoření nové instance ta stará pozastaví a "počká" na dokončení té nové. Tím pádem to není úplně použitelné pokud člověk předpokládá, že se příkazy v obou instancích budou vykonávat postupně tak, jak jsou za sebou seřazené.

Aktuální verze scriptu ke stažení je přiložena na konci tohoto příspěvku.
Je k dispozici verze 0.2.0 doplňku. Kromě opravy drobných chyb, které neměly vliv na funkci  a vylepšení kódu, byl přidán nový interní příkaz (<time>) - s jedním parametrem, kterým je čas čekání ve vteřinách.

Příkaz je kombinací builtin příkazu Action(Stop) a interního příkazu (<time>). V případě, pokud se v době spuštění scriptu něco přehrává, provede script příkaz k ukončení přehrávání a pak čeká zadaný čas. Pokud se ale v době spuštění nic nepřehrává, tak script nečeká.

S využitím tohoto příkazu je možné nahradit volání scriptu ve tvaru:
 
Kód:
RunScript(script.macro,Action(Stop),@Delay(5),Suspend())

novou verzí:
 
Kód:
RunScript(script.macro,@StopAndDelay(5),Suspend())

Výsledek bude stejný, tzn. dojde k uspání boxu, jen v případě, pokud se v okamžiku volání scriptu nebude nic přehrávat, tak ani neproběhne prodleva 5 vteřin, která v takovém případě nemá žádný smysl.

Toto je příklad toho, o čem píši v 1. příspěvku. Pokud někoho napadne nějaká funkce nebo kombinace funkcí, pro které je nutné nebo účelné vytvořit nový interní příkaz, klidně se ozvěte a navrhněte, co by takový příkaz měl dělat nebo jak a za jakých podmínek by měl fungovat. Pokud to bude možné, tak ho do scriptu rád přidám.
(15.7.2025, 17:56)JiRo Napsal(a): [ -> ]...Protože parametrem volání může být interní příkaz, je možné ve volání scriptu zavolat i samotný script.macro s vlastními (i jinými) parametry. Vytvoří se nová instance, na té původní nezávislá.


Tedy tvůj doplněk script.makro může volat znovu script.makro. Pokud napřiklad běží Kodi na jiném Linuxu (ne na xxxELEC), může jako poslední v řetězu příkazů volat i xyzscript.sh v operačním systému?
: Tedy já jsem to spuštění dalšího script.macra nezkoušel, ale není důvod, proč by to nemělo jít.

Jinak platí, že volat můžeš cokoliv, co ti umožní vhodný builtin (a nebo interní) příkaz. Takže chceš-li spustit z Kodi v systému sh script, můžeš pro to použít builtin příkazy System.Exec() a System.ExecWait(). Ale nejsem si jistý, jak pak vypadá závislost mezi rodičovským procesem, (v tomto případě Kodi) a potomkem (tedy spuštěným sh script-em), pokud se Kodi ukončí.

Jinak by samozřejmě bylo možné vytvořit další interní příkaz, který by mohl pomocí python knihovního modulu např.subprocess spouštět sh scripty, nebo vlastně cokoliv v systému, přímo. Tam se pak dá dělat hodně dalších kouzel, které ty dva výše uvedené Kodi builtin příkazy nedokáží.
: Tak s tím spouštěním další instance script.macro jsem šlápnul trochu vedle...

Tak ono to jde, ale je tam potíž, že spuštění další instance se nechová tak, jak by člověk nějak intuitivně předpokládal. Tedy že ta původní instance počká, až ta nová doběhne a pak bude pokračovat dál. Takhle to bohužel nefunguje. Ono to vlastně v gui Kodi není vždycky tak, jak jsme zvyklí z chování úloh v operačních systémech. Kde se úloha spustí, provede nějakou akci a skončí, většinou s nějakým výsledkem, a pak se, třeba, spustí další. Tady se jednotlivé příkazy sice vykonávají sekvenčně, ale není to tak, že by se čekalo na jejich provedení. Záleží tady samozřejmě na tom, o jaké příkazy jde, respektive, co je jejich podstatou, ale ve většině případů se to chová tak, že se ty builtin příkazy ze skriptu vysypu jeden za druhým (z pohledu uživatele víceméně současně) a navíc, script ani nemá možnost dozvědět se, co a jak se vlastně vykonalo a jak to dopadlo. Ostatně, proto jsem jako první do scriptu zařadil interní příkaz Delay(), který tohle může do jisté míry ovlivnit.  Ano jsou výjimky, např. již zmíněný System.ExecWait() nebo právě ty interní příkazy scriptu, kdy se čekat na jejich vykonání může, ale to jsou právě jen výjimky, případně speciální případy.

Takže se omlouvám, jestli jsem tě svým vyjádření ohledně rekurzivního volání scriptu zavedl na špatnou cestu. Ukazuje, že by si to člověk před tím, než něco vypustí, měl opravdu pořádně vyzkoušet a hlavně, nenechat se vést zkratkovitým uvažováním. Něco ve smyslu parafráze známé moudrosti, Dvakrát se zamysli, případně i vyzkoušej, než něco jednou napíšeš. Já se tím většinou řídím, ale teď je období, kdy mám plno jiné práce a tak to flákám...
Díky za upřesnění. Budu sám zkoušet ale až nebudu mít jiné "úkoly".
Jak jsem už psal, zajímalo mne volat xyzscript.sh (v systému) až jako poslední. Tedy abych použil tvůj příklad:
Opravil jsem editací!
Kód:
RunScript(script.macro,Action(Stop),@Delay(5),System.Exec(/<cesta>/xyzscript.sh))
Pokud jsem tedy zapsal vnořenou syntaxi správně. Nenapsal, opraveno viz dále JiRo.

Nebo by se volání xyzscript.sh muselo udělat jako volání interního příkazu runscript s parametrem, podobně jako vytvořené delay(), tedy jako:
(/<cesta>/xyzscript.sh)..?
: Chceš-li spustit příkaz shellu v systému, musíš použít Kodi builtin příkaz System.Exec(exec) nebo System.ExecWait(exec). Kde v parametru exec budeš mít kompletní příkaz pro spuštění sh scriptu xyzscript.sh, včetně úplné cesty (upřímně nevím, jakou default cestu Kodi v tomto případě očekává, proto tam já vždycky dávám kompletní cestu a také, v případ sh scriptu, nastavuji soubor jako spustitelný - jesti je to nutné, nevím, tohle jsem nezjišťoval, prostě to tak dělám vždy). Takže např. nějak takto:
 
Kód:
RunScript(script.macro,Action(Stop),@Delay(5),System.Exec(/storage/xyzscript.sh))

Jen na vysvětlení,  tebou uvádění příkaz RunScript(), ten slouží ke spuštění Kodi doplňku typu script, kterému je možné při spuštění předat parametry (což se ostatně děje právě při spouštění script.macro). Viz List of built-in functions#Add-on built-in's na rozdíl od List of built-in functions#System built-in's.

Ale ano, jak píšeš a jak jsem psal i já, bylo by také možné vytvořit další interní příkaz scriptu script.macro, který by mohl úlohy v systému spouštět také a mít i další funkce. Např. zpracovat návratový kódy a nebo dokonce např. převzít data ze stdout spouštěné aplikace. Ale pro tento tvůj případ to zjevně nutné není.
Na žádost jsem do popisu v 1. příspěvku přidal další informace týkající se zpracování příkazů, chování při chybách, kontrol syntaxe a typů parametrů a také možnosti použití defaultních hodnot parametrů.